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

   /* 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,
         (LPOLESTR)APPNAME,
         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;
   OLECHAR                 szFile[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=(LPOLESTR)szFile;
   io.cchFile=OLEUI_CCHPATHMAX;
//   _fmemset((LPOLESTR)szFile, 0, OLEUI_CCHPATHMAX);
   _fmemset((LPOLESTR)szFile, 0, OLEUI_CCHPATHMAX*sizeof(OLECHAR));

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

   lpContainerLine = ContainerLine_Create(
         dwOleCreateType,
         hDC,
         nTab,
         lpContainerDoc,
         &io.clsid,
         (LPOLESTR)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(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];
   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(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 = (LPOLESTR)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,
            (LPOLESTR 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,128,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 = 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)
      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,
      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 )
      /* 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_EXCLUSIVE|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,
      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 = GetScode(hrErr);
      goto error;
   }

error:
   // release artificial AddRef
   CntrDoc_LinkCont_Release(lpThis);

   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;

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

error:
   OLEDBG_END2

   // release artificial AddRef
   CntrDoc_LinkCont_Release(lpThis);

   return ResultFromScode(sc);
}


STDMETHODIMP CntrDoc_LinkCont_SetLinkSource(
      LPOLEUILINKCONTAINER    lpThis,
      DWORD                   dwLink,
      LPOLESTR                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;


   // 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) {

      /* 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,
               (LPOLESTR FAR*)&lpContainerLine->m_lpszShortType
         );
         OLEDBG_END2
      }
      else
         lpContainerLine->m_fLinkUnavailable = TRUE;
   }
   else {
      LPMONIKER   lpmkFile = NULL;
      LPMONIKER   lpmkItem = NULL;
      OLECHAR     szDelim[2];
      LPOLESTR    lpszName;

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

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

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

      if (!lpmkFile)
         goto cleanup;

      if (OLESTRLEN(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, (LPCOLESTR)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
   // release artificial AddRef
   CntrDoc_LinkCont_Release(lpThis);

   return ResultFromScode(sc);
}


STDMETHODIMP CntrDoc_LinkCont_GetLinkSource(
      LPOLEUILINKCONTAINER    lpThis,
      DWORD                   dwLink,
      LPOLESTR FAR*           lplpszDisplayName,
      ULONG FAR*              lplenFileName,
      LPOLESTR FAR*           lplpszFullLinkType,
      LPOLESTR 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;

   // artificial AddRef in case object is released during call
   CntrDoc_LinkCont_AddRef(lpThis);

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

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

   // release artificial AddRef
   CntrDoc_LinkCont_Release(lpThis);

   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;
   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 ResultFromScode(NOERROR);

error:
   OutlineApp_ErrorMessage(g_lpApp, OLESTR("Could not break the link."));
   OLEDBG_END2
   // release artificial AddRef
   CntrDoc_LinkCont_Release(lpThis);

   return ResultFromScode(E_FAIL);
}
