/*************************************************************************
**
**    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 - 1993 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
char ErrMsgShowObj[] = "Could not show object server!";
char ErrMsgInsertObj[] = "Insert Object failed!";
char ErrMsgConvertObj[] = "Convert Object failed!";
char ErrMsgCantConvert[] = "Unable to convert the selection!";
char ErrMsgActivateAsObj[] = "Activate As Object failed!";

extern char ErrMsgSaving[];
extern char 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));

	/* OLE2NOTE: 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,
			(LPSTR)APPNAME,
			cLinks)) {
		if (ID_PU_LINKS == OleUIPromptUser(
				(WORD)IDD_CANNOTUPDATELINK,
				hwndDoc,
				(LPSTR)APPNAME)) {
			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;
	char                    szStgName[CWCSTORAGENAME];
	UINT                    uRet;
	OLEUIINSERTOBJECT       io;
	char                    szFile[OLEUI_CCHPATHMAX];
	DWORD                   dwOleCreateType;
	BOOL                    fDisplayAsIcon;
	HCURSOR                 hPrevCursor;

	_fmemset((LPOLEUIINSERTOBJECT)&io, 0, sizeof(io));
	io.cbStruct=sizeof(io);
	io.dwFlags=IOF_SELECTCREATENEW | IOF_SHOWHELP;
	io.hWndOwner=((LPOUTLINEDOC)lpContainerDoc)->m_hWndDoc;
	io.lpszFile=(LPSTR)szFile;
	io.cchFile=sizeof(szFile);
	_fmemset((LPSTR)szFile, 0, OLEUI_CCHPATHMAX);

#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, sizeof(szStgName));

	/* 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;

	lpContainerLine = ContainerLine_Create(
			dwOleCreateType,
			hDC,
			nTab,
			lpContainerDoc,
			&io.clsid,
			(LPSTR)szFile,
			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).
	**    OLE2NOTE: 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);
		}

		/* OLE2NOTE: 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) {
		/* OLE2NOTE: 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);

			/* OLE2NOTE: 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(el));
	el.cbStruct=sizeof(el);
	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;
	LPSTR        lpErrMsg = NULL;
	HRESULT      hrErr;
	HCURSOR      hPrevCursor;
	BOOL         fMustRun = FALSE;
	BOOL         fMustClose = FALSE;
	BOOL         fObjConverted = FALSE;
	BOOL         fDisplayChanged = FALSE;
	BOOL         fHaveCLSID = FALSE;
	BOOL         fHaveFmtUserType = FALSE;
	char         szUserType[128];
	BOOL         fMustActivate;

	/* OLE2NOTE: 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(ct));

	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 = (LPSTR)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) {
		/* OLE2NOTE: 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,
				(LPSTR FAR*)&ct.lpszUserType);
		if (hrErr == NOERROR)
			fHaveFmtUserType = TRUE;
		else {
			OleDbgOutHResult("ReadFmtUserTypeStg returned", hrErr);
		}
	} else {
		/* OLE2NOTE: 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, &ct.lpszDefLabel);
		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,sizeof(szUserType),NULL)) {
			ct.lpszUserType = OleStdCopyString(szUserType, NULL);
		} 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) {

		/*****************************************************************
		**  OLE2NOTE: 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.
			**
			** OLE2NOTE: 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.
			**
			** OLE2NOTE: 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")
			hrErr = OleStdDoTreatAsClass(ct.lpszUserType, (REFCLSID)&ct.clsid,
					(REFCLSID)&ct.clsidNew);
			OLEDBG_END2

			// Reload the object
			ContainerLine_LoadOleObject(lpContainerLine);

			fMustActivate = TRUE;   // we should activate this object
		}

		/*****************************************************************
		**  OLE2NOTE: 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.
			**
			** OLE2NOTE: 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) && GetScode(hrErr)!=OLE_E_STATIC) {

				// ERROR: convert of the object failed.
				// revert the storage to restore the original link.
				// (OLE2NOTE: 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);

				/* OLE2NOTE: 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 = 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)
		OleStdFreeString(ct.lpszUserType, NULL);

	if (ct.lpszDefLabel)
		OleStdFreeString(ct.lpszDefLabel, NULL);

	if (ct.hMetaPict)
		OleUIMetafilePictIconFree(ct.hMetaPict);    // clean up metafile

	SetCursor(hPrevCursor);     // restore original cursor

	return;

error:
	if (ct.lpszUserType)
		OleStdFreeString(ct.lpszUserType, NULL);

	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 )
	/* OLE2NOTE: 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,
		LPCSTR                  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, (LPSTR)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 )
		/* OLE2NOTE: 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;

		/* OLE2NOTE: 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_DENY_WRITE|STGM_CREATE,
				0,
				&lpDestStg
		);
		OleDbgAssertSz(hrErr == NOERROR, "Could not create Docfile");
		if (hrErr != NOERROR) {
			OleDbgOutHResult("StgCreateDocfile returned", hrErr);
			goto error;
		}
		fMustRelDestStg = TRUE;
	}

	/*  OLE2NOTE: 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,
		LPSTR                   lpszStgName,
		int                     nLen
)
{
	wsprintf(lpszStgName, "%s %ld",
			(LPSTR)DEFOBJNAMEPREFIX,
			++(lpContainerDoc->m_nNextObjNo)
	);

	while (ContainerDoc_IsStgNameUsed(lpContainerDoc, lpszStgName) == TRUE) {
		wsprintf(lpszStgName, "%s %ld",
				(LPSTR)DEFOBJNAMEPREFIX,
				++(lpContainerDoc->m_nNextObjNo)
		);
	}
}


/* ContainerDoc_IsStgNameUsed
** --------------------------
**    Check if a given StgName is already in use.
*/
BOOL ContainerDoc_IsStgNameUsed(
		LPCONTAINERDOC          lpContainerDoc,
		LPSTR                   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 (lstrcmp(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, "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;

	OLEDBG_BEGIN2("CntrDoc_LinkCont_GetNextLink\r\n")

	lpContainerLine = ContainerDoc_GetNextLink(
			lpContainerDoc,
			(LPCONTAINERLINE)dwLink
	);

	OLEDBG_END2
	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;

	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 = GetScode(hrErr);
		goto error;
	}

	OLEDBG_END2
	return ResultFromScode(sc);

error:
	OLEDBG_END2
	return ResultFromScode(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;

	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 = GetScode(hrErr);
		goto error;
	}

	OLEDBG_END2
	return ResultFromScode(sc);

error:
	OLEDBG_END2
	return ResultFromScode(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;


	OLEDBG_BEGIN2("CntrDoc_LinkCont_SetLinkSource\r\n")

	OleDbgAssert(lpContainerLine);

	lpContainerLine->m_fLinkUnavailable = TRUE;

	if (fValidateSource) {

		/* OLE2NOTE: 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 = GetScode(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;
		}

		hrErr = OleStdMkParseDisplayName(
			  &clsidOld,lpbc,lpszDisplayName,lpchEaten,(LPMONIKER FAR*)&lpmk);

		if (hrErr != NOERROR) {
			sc = GetScode(hrErr);   // ERROR in parsing moniker!
			goto cleanup;
		}
		/* OLE2NOTE: 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,
					(LPSTR FAR*)&lpContainerLine->m_lpszShortType
			);
			OLEDBG_END2
		}
		else
			lpContainerLine->m_fLinkUnavailable = TRUE;
	}
	else {
		LPMONIKER   lpmkFile = NULL;
		LPMONIKER   lpmkItem = NULL;
		char        szDelim[2];
		LPSTR       lpszName;

		szDelim[0] = lpszDisplayName[(int)lenFileName];
		szDelim[1] = '\0';
		lpszDisplayName[(int)lenFileName] = '\0';

		OLEDBG_BEGIN2("CreateFileMoniker called\r\n")
		CreateFileMoniker((LPSTR)lpszDisplayName, (LPMONIKER FAR*)&lpmkFile);
		OLEDBG_END2

		lpszDisplayName[(int)lenFileName] = szDelim[0];

		if (!lpmkFile)
			goto cleanup;

		if (lstrlen(lpszDisplayName) > (int)lenFileName) {  // have item name
			lpszName = lpszDisplayName + lenFileName + 1;

			OLEDBG_BEGIN2("CreateItemMoniker called\r\n")
			CreateItemMoniker(
					szDelim, lpszName, (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(GetScode(hrErr))) {
			OleDbgOutHResult("IOleLink::SetSourceMoniker returned",hrErr);
			sc = GetScode(hrErr);
			goto cleanup;
		}

		/* OLE2NOTE: 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(GetScode(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(GetScode(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(GetScode(hrErr)))
				OleDbgOutHResult("IOleLink::BindIfRunning returned",hrErr);
#endif
		}
	} else {
		/* OLE2NOTE: 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, (LPCSTR)lpszDisplayName);
		OLEDBG_END2

		if (hrErr != NOERROR) {
			OleDbgOutHResult("IOleLink::SetSourceDisplayName returned",hrErr);
			sc = GetScode(hrErr);
			goto cleanup;
		}
	}

cleanup:
	if (lpLinkSrcOleObj)
		OleStdRelease((LPUNKNOWN)lpLinkSrcOleObj);
	if (lpmk)
		OleStdRelease((LPUNKNOWN)lpmk);
	if (lpbc)
		OleStdRelease((LPUNKNOWN)lpbc);

	OLEDBG_END2
	return ResultFromScode(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;

	OLEDBG_BEGIN2("CntrDoc_LinkCont_GetLinkSource\r\n")

	/* OLE2NOTE: we must make sure to set all out parameters to NULL. */
	*lplpszDisplayName  = NULL;
	*lplpszFullLinkType = NULL;
	*lplpszShortLinkType= NULL;
	*lplenFileName      = 0;
	*lpfSourceAvailable = !lpContainerLine->m_fLinkUnavailable;

	OleDbgAssert(lpContainerLine);

	if (! lpOleLink) {
		OLEDBG_END2
		return ResultFromScode(E_FAIL);
	}

	OLEDBG_BEGIN2("IOleLink::GetSourceMoniker called\r\n")
	hrErr = lpOleLink->lpVtbl->GetSourceMoniker(
			lpOleLink,
			(LPMONIKER FAR*)&lpmk
	);
	OLEDBG_END2

	if (hrErr == NOERROR) {
		/* OLE2NOTE: 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);
		if (lpOleObj) {
			lpOleObj->lpVtbl->GetUserType(
					lpOleObj,
					USERCLASSTYPE_FULL,
					lplpszFullLinkType
			);
			lpOleObj->lpVtbl->GetUserType(
					lpOleObj,
					USERCLASSTYPE_SHORT,
					lplpszShortLinkType
			);
			OleStdRelease((LPUNKNOWN)lpOleObj);
		}
		*lplenFileName = OleStdGetLenFilePrefixOfMoniker(lpmk);
		lpmk->lpVtbl->Release(lpmk);
	}

	OLEDBG_BEGIN2("IOleLink::GetSourceDisplayName called\r\n")
	hrErr = lpOleLink->lpVtbl->GetSourceDisplayName(
			lpOleLink,
			lplpszDisplayName
	);
	OLEDBG_END2

	if (hrErr != NOERROR) {
		OleDbgOutHResult("IOleLink::GetSourceDisplayName returned", hrErr);
		OLEDBG_END2
		return hrErr;
	}

	OLEDBG_END2

	if (lpfIsSelected)
		*lpfIsSelected = Line_IsSelected((LPLINE)lpContainerLine);

	return NOERROR;
}


STDMETHODIMP CntrDoc_LinkCont_OpenLinkSource(
		LPOLEUILINKCONTAINER    lpThis,
		DWORD                   dwLink
)
{
	LPCONTAINERDOC lpContainerDoc =
			((struct CDocOleUILinkContainerImpl FAR*)lpThis)->lpContainerDoc;
	LPCONTAINERLINE lpContainerLine = (LPCONTAINERLINE)dwLink;
	SCODE sc = S_OK;

	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
	return ResultFromScode(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;
	HRESULT hrErr;

	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
	}

	/* OLE2NOTE: 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 (GetScode(hrErr) == OLE_E_CLASSDIFF)
		hrErr = ContainerLine_ReCreateLinkBecauseClassDiff(lpContainerLine);

	lpContainerLine->m_fLinkUnavailable = (hrErr != NOERROR);

	if (hrErr != NOERROR) {
		OleDbgOutHResult("IOleObject::Update returned", hrErr);
		sc = GetScode(hrErr);
		if (fErrorMessage) {
			ContainerLine_ProcessOleRunError(
					lpContainerLine,hrErr,fErrorAction,FALSE/*fMenuInvoked*/);
		}
	}
	/* OLE2NOTE: 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
	return ResultFromScode(sc);
}


/* CntrDoc_LinkCont_CancelLink
** ---------------------------
**    Convert the link to a static picture.
**
**    OLE2NOTE: 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;
	char                szStgName[CWCSTORAGENAME];
	LPCONTAINERLINE     lpNewContainerLine = NULL;
	LPDATAOBJECT        lpSrcDataObj;
	LPOLELINK           lpOleLink = lpContainerLine->m_lpOleLink;
	int nIndex = LineList_GetLineIndex(lpLL, (LPLINE)lpContainerLine);

	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, sizeof(szStgName));
	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
	return ResultFromScode(NOERROR);

error:
	OutlineApp_ErrorMessage(g_lpApp, "Could not break the link.");
	OLEDBG_END2
	return ResultFromScode(E_FAIL);
}
