/*************************************************************************
**
**    OLE 2 Container Sample Code
**
**    cntrline.c
**
**    This file contains ContainerLine methods.
**
**    (c) Copyright Microsoft Corp. 1992 - 1993 All Rights Reserved
**
*************************************************************************/

#include "outline.h"

OLEDBGDATA



extern LPOUTLINEAPP         g_lpApp;
extern IUnknownVtbl         g_CntrLine_UnknownVtbl;
extern IOleClientSiteVtbl   g_CntrLine_OleClientSiteVtbl;
extern IAdviseSinkVtbl      g_CntrLine_AdviseSinkVtbl;

#if defined( INPLACE_CNTR )
extern IOleInPlaceSiteVtbl  g_CntrLine_OleInPlaceSiteVtbl;
extern BOOL g_fInsideOutContainer;
#endif  // INPLACE_CNTR

// REVIEW: should use string resource for messages
OLECHAR ErrMsgDoVerb[] = OLESTR("OLE object action failed!");


/* prototype for static functions */
static void InvertDiffRect(LPRECT lprcPix, LPRECT lprcObj, HDC hDC);


/*************************************************************************
** ContainerLine
**    This object represents the location within the container where
**    the embedded/linked object lives. It exposes interfaces to the
**    object that allow the object to get information about its
**    embedding site and to announce notifications of important events
**    (changed, closed, saved)
**
**    The ContainerLine exposes the following interfaces:
**      IUnknown
**      IOleClientSite
**      IAdviseSink
*************************************************************************/



/*************************************************************************
** ContainerLine::IUnknown interface implementation
*************************************************************************/


// IUnknown::QueryInterface
STDMETHODIMP CntrLine_Unk_QueryInterface(
      LPUNKNOWN           lpThis,
      REFIID              riid,
      LPVOID FAR*         lplpvObj
)
{
   LPCONTAINERLINE lpContainerLine =
         ((struct COleClientSiteImpl FAR*)lpThis)->lpContainerLine;

   return ContainerLine_QueryInterface(lpContainerLine, riid, lplpvObj);
}


// IUnknown::AddRef
STDMETHODIMP_(ULONG) CntrLine_Unk_AddRef(LPUNKNOWN lpThis)
{
   LPCONTAINERLINE lpContainerLine =
         ((struct COleClientSiteImpl FAR*)lpThis)->lpContainerLine;

   OleDbgAddRefMethod(lpThis, "IUnknown");

   return ContainerLine_AddRef(lpContainerLine);
}


// IUnknown::Release
STDMETHODIMP_(ULONG) CntrLine_Unk_Release(LPUNKNOWN lpThis)
{
   LPCONTAINERLINE lpContainerLine =
         ((struct COleClientSiteImpl FAR*)lpThis)->lpContainerLine;

   OleDbgReleaseMethod(lpThis, "IUnknown");

   return ContainerLine_Release(lpContainerLine);
}


/*************************************************************************
** ContainerLine::IOleClientSite interface implementation
*************************************************************************/

// IOleClientSite::QueryInterface
STDMETHODIMP CntrLine_CliSite_QueryInterface(
      LPOLECLIENTSITE     lpThis,
      REFIID              riid,
      LPVOID FAR*         lplpvObj
)
{
   LPCONTAINERLINE lpContainerLine =
         ((struct COleClientSiteImpl FAR*)lpThis)->lpContainerLine;

   return ContainerLine_QueryInterface(lpContainerLine, riid, lplpvObj);
}


// IOleClientSite::AddRef
STDMETHODIMP_(ULONG) CntrLine_CliSite_AddRef(LPOLECLIENTSITE lpThis)
{
   LPCONTAINERLINE lpContainerLine =
         ((struct COleClientSiteImpl FAR*)lpThis)->lpContainerLine;

   OleDbgAddRefMethod(lpThis, "IOleClientSite");

   return ContainerLine_AddRef(lpContainerLine);
}


// IOleClientSite::Release
STDMETHODIMP_(ULONG) CntrLine_CliSite_Release(LPOLECLIENTSITE lpThis)
{
   LPCONTAINERLINE lpContainerLine =
         ((struct COleClientSiteImpl FAR*)lpThis)->lpContainerLine;

   OleDbgReleaseMethod(lpThis, "IOleClientSite");

   return ContainerLine_Release(lpContainerLine);
}


// IOleClientSite::SaveObject
STDMETHODIMP CntrLine_CliSite_SaveObject(LPOLECLIENTSITE lpThis)
{
   LPCONTAINERLINE lpContainerLine =
         ((struct COleClientSiteImpl FAR*)lpThis)->lpContainerLine;
   LPPERSISTSTORAGE lpPersistStg = lpContainerLine->m_lpPersistStg;
   SCODE sc = S_OK;
   HRESULT hrErr;

   OLEDBG_BEGIN2("CntrLine_CliSite_SaveObject\r\n")

   if (! lpPersistStg) {
      /* OLE2NOTE: The object is NOT loaded. a container must be
      **    prepared for the fact that an object that it thinks it
      **    has unloaded may still call IOleClientSite::SaveObject.
      **    in this case the container should fail the save call and
      **    NOT try to reload the object. this scenario arises if you
      **    have a in-process server (DLL object) which has a
      **    connected linking client. even after the embedding
      **    container unloads the DLL object, the link connection
      **    keeps the object alive. it may then as part of its
      **    shutdown try to call IOCS::SaveObject, but then it is too
      **    late.
      */
      OLEDBG_END2

      return ResultFromScode(E_FAIL);
   }

   // mark ContainerDoc as now dirty
   OutlineDoc_SetModified(
         (LPOUTLINEDOC)lpContainerLine->m_lpDoc, TRUE, TRUE, FALSE);

   /* Tell OLE object to save itself
   ** OLE2NOTE: it is NOT sufficient to ONLY call
   **    IPersistStorage::Save method. it is also necessary to call
   **    WriteClassStg. the helper API OleSave does this automatically.
   */
   OLEDBG_BEGIN2("OleSave called\r\n")
   hrErr=OleSave(lpPersistStg,lpContainerLine->m_lpStg, TRUE/*fSameAsLoad*/);
   OLEDBG_END2

   if (hrErr != NOERROR) {
      OleDbgOutHResult("WARNING: OleSave returned", hrErr);
      sc = GetScode(hrErr);
   }

   // OLE2NOTE: even if OleSave fails, SaveCompleted must be called.
   OLEDBG_BEGIN2("IPersistStorage::SaveCompleted called\r\n")
   hrErr = lpPersistStg->lpVtbl->SaveCompleted(lpPersistStg, NULL);
   OLEDBG_END2

   if (hrErr != NOERROR) {
      OleDbgOutHResult("WARNING: SaveCompleted returned",hrErr);
      if (sc == S_OK)
         sc = GetScode(hrErr);
   }

   OLEDBG_END2
   return ResultFromScode(sc);
}


// IOleClientSite::GetMoniker
STDMETHODIMP CntrLine_CliSite_GetMoniker(
      LPOLECLIENTSITE     lpThis,
      DWORD               dwAssign,
      DWORD               dwWhichMoniker,
      LPMONIKER FAR*      lplpmk
)
{
   LPCONTAINERLINE lpContainerLine;

   lpContainerLine=((struct COleClientSiteImpl FAR*)lpThis)->lpContainerLine;

   OLEDBG_BEGIN2("CntrLine_CliSite_GetMoniker\r\n")

   // OLE2NOTE: we must make sure to set output pointer parameters to NULL
   *lplpmk = NULL;

   switch (dwWhichMoniker) {

      case OLEWHICHMK_CONTAINER:
         /* OLE2NOTE: create a FileMoniker which identifies the
         **    entire container document.
         */
         *lplpmk = OleDoc_GetFullMoniker(
               (LPOLEDOC)lpContainerLine->m_lpDoc,
               dwAssign
         );
         break;

      case OLEWHICHMK_OBJREL:

         /* OLE2NOTE: create an ItemMoniker which identifies the
         **    OLE object relative to the container document.
         */
         *lplpmk = ContainerLine_GetRelMoniker(lpContainerLine, dwAssign);
         break;

      case OLEWHICHMK_OBJFULL:
      {
         /* OLE2NOTE: create an absolute moniker which identifies the
         **    OLE object in the container document. this moniker is
         **    created as a composite of the absolute moniker for the
         **    entire document appended with an item moniker which
         **    identifies the OLE object relative to the document.
         */

         *lplpmk = ContainerLine_GetFullMoniker(lpContainerLine, dwAssign);
         break;
      }
   }

   OLEDBG_END2

   if (*lplpmk != NULL)
      return NOERROR;
   else
      return ResultFromScode(E_FAIL);
}


// IOleClientSite::GetContainer
STDMETHODIMP CntrLine_CliSite_GetContainer(
      LPOLECLIENTSITE     lpThis,
      LPOLECONTAINER FAR* lplpContainer
)
{
   LPCONTAINERLINE lpContainerLine;
   HRESULT hrErr;

   // artificial AddRef in case object is deleted during call
   CntrLine_CliSite_AddRef(lpThis);
   OLEDBG_BEGIN2("CntrLine_CliSite_GetContainer\r\n")

   lpContainerLine=((struct COleClientSiteImpl FAR*)lpThis)->lpContainerLine;

   hrErr = OleDoc_QueryInterface(
         (LPOLEDOC)lpContainerLine->m_lpDoc,
         &IID_IOleContainer,
         (LPVOID FAR*)lplpContainer
   );

   OLEDBG_END2
   // release artificial AddRef
   CntrLine_CliSite_Release(lpThis);
   return hrErr;
}


// IOleClientSite::ShowObject
STDMETHODIMP CntrLine_CliSite_ShowObject(LPOLECLIENTSITE lpThis)
{
   LPCONTAINERLINE lpContainerLine =
         ((struct COleClientSiteImpl FAR*)lpThis)->lpContainerLine;
   LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC)lpContainerLine->m_lpDoc;
   LPLINELIST lpLL = OutlineDoc_GetLineList(lpOutlineDoc);
   int nIndex = LineList_GetLineIndex(lpLL, (LPLINE)lpContainerLine);
   HWND hWndFrame = OutlineApp_GetFrameWindow(g_lpApp);

   // artificial AddRef in case object is deleted during call
   CntrLine_CliSite_AddRef(lpThis);

   OLEDBG_BEGIN2("CntrLine_CliSite_ShowObject\r\n")

   /* make sure our doc window is visible and not minimized.
   **    the OutlineDoc_ShowWindow function will cause the app window
   **    to show itself SW_SHOWNORMAL.
   */
   if (! IsWindowVisible(hWndFrame) || IsIconic(hWndFrame))
      OutlineDoc_ShowWindow(lpOutlineDoc);

   BringWindowToTop(hWndFrame);

   /* make sure that the OLE object is currently in view. if necessary
   **    scroll the document in order to bring it into view.
   */
   LineList_ScrollLineIntoView(lpLL, nIndex);

#if defined( INPLACE_CNTR )
   /* after the in-place object is scrolled into view, we need to ask
   **    it to update its rect for the new clip rect coordinates
   */
   ContainerDoc_UpdateInPlaceObjectRects((LPCONTAINERDOC)lpOutlineDoc, 0);
#endif

   OLEDBG_END2
   // release artificial AddRef
   CntrLine_CliSite_Release(lpThis);
   return NOERROR;
}


// IOleClientSite::OnShowWindow
STDMETHODIMP CntrLine_CliSite_OnShowWindow(LPOLECLIENTSITE lpThis, BOOL fShow)
{
   LPCONTAINERLINE lpContainerLine =
         ((struct COleClientSiteImpl FAR*)lpThis)->lpContainerLine;
   LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC)lpContainerLine->m_lpDoc;
   LPLINELIST lpLL = OutlineDoc_GetLineList(lpOutlineDoc);
   int nIndex = LineList_GetLineIndex(lpLL, (LPLINE)lpContainerLine);

   // artificial AddRef in case object is deleted during call
   CntrLine_CliSite_AddRef(lpThis);

   if (fShow) {
      OLEDBG_BEGIN2("CntrLine_CliSite_OnShowWindow(TRUE)\r\n")

      /* OLE2NOTE: we need to hatch out the OLE object now; it has
      **    just been opened in a window elsewhere (open editing as
      **    opposed to in-place activation).
      **    force the line to re-draw with the hatch.
      */
      lpContainerLine->m_fObjWinOpen = TRUE;
      LineList_ForceLineRedraw(lpLL, nIndex, FALSE /*fErase*/);

   } else {
      OLEDBG_BEGIN2("CntrLine_CliSite_OnShowWindow(FALSE)\r\n")

      /* OLE2NOTE: the object associated with this container site has
      **    just closed its server window. we should now remove the
      **    hatching that indicates that the object is open
      **    elsewhere. also our window should now come to the top.
      **    force the line to re-draw without the hatch.
      */
      lpContainerLine->m_fObjWinOpen = FALSE;
      LineList_ForceLineRedraw(lpLL, nIndex, TRUE /*fErase*/);

      BringWindowToTop(lpOutlineDoc->m_hWndDoc);
      SetFocus(lpOutlineDoc->m_hWndDoc);
   }

   OLEDBG_END2
   // release artificial AddRef
   CntrLine_CliSite_Release(lpThis);

   return NOERROR;
}


// IOleClientSite::RequestNewObjectLayout
STDMETHODIMP CntrLine_CliSite_RequestNewObjectLayout(LPOLECLIENTSITE lpThis)
{
   OleDbgOut2("CntrLine_CliSite_RequestNewObjectLayout\r\n");

   /* OLE2NOTE: this method is NOT yet used. it is for future layout
   **    negotiation support.
   */
   return ResultFromScode(E_NOTIMPL);
}


/*************************************************************************
** ContainerLine::IAdviseSink interface implementation
*************************************************************************/

// IAdviseSink::QueryInterface
STDMETHODIMP CntrLine_AdvSink_QueryInterface(
      LPADVISESINK        lpThis,
      REFIID              riid,
      LPVOID FAR*         lplpvObj
)
{
   LPCONTAINERLINE lpContainerLine =
         ((struct COleClientSiteImpl FAR*)lpThis)->lpContainerLine;

   return ContainerLine_QueryInterface(lpContainerLine, riid, lplpvObj);
}


// IAdviseSink::AddRef
STDMETHODIMP_(ULONG) CntrLine_AdvSink_AddRef(LPADVISESINK lpThis)
{
   LPCONTAINERLINE lpContainerLine =
         ((struct COleClientSiteImpl FAR*)lpThis)->lpContainerLine;

   OleDbgAddRefMethod(lpThis, "IAdviseSink");

   return ContainerLine_AddRef(lpContainerLine);
}


// IAdviseSink::Release
STDMETHODIMP_(ULONG) CntrLine_AdvSink_Release (LPADVISESINK lpThis)
{
   LPCONTAINERLINE lpContainerLine =
         ((struct COleClientSiteImpl FAR*)lpThis)->lpContainerLine;

   OleDbgReleaseMethod(lpThis, "IAdviseSink");

   return ContainerLine_Release(lpContainerLine);
}


// IAdviseSink::OnDataChange
STDMETHODIMP_(void) CntrLine_AdvSink_OnDataChange(
      LPADVISESINK        lpThis,
      FORMATETC FAR*      lpFormatetc,
      STGMEDIUM FAR*      lpStgmed
)
{
   OleDbgOut2("CntrLine_AdvSink_OnDataChange\r\n");
   // We are not interested in data changes (only view changes)
   //      (ie. nothing to do)
}


STDMETHODIMP_(void) CntrLine_AdvSink_OnViewChange(
      LPADVISESINK        lpThis,
      DWORD               aspects,
      LONG                lindex
)
{
   LPCONTAINERLINE lpContainerLine;
   LPOUTLINEDOC lpOutlineDoc;
   HWND hWndDoc;
   LPLINELIST lpLL;
   MSG msg;
   int nIndex;

   // artificial AddRef in case object is destroyed during call
   CntrLine_AdvSink_AddRef(lpThis);

   OLEDBG_BEGIN2("CntrLine_AdvSink_OnViewChange\r\n")

   lpContainerLine = ((struct CAdviseSinkImpl FAR*)lpThis)->lpContainerLine;
   lpOutlineDoc = (LPOUTLINEDOC)lpContainerLine->m_lpDoc;

   /* OLE2NOTE: at this point we simply invalidate the rectangle of
   **    the object to force a repaint in the future, we mark
   **    that the extents of the object may have changed
   **    (m_fDoGetExtent=TRUE), and we post a message
   **    (WM_U_UPDATEOBJECTEXTENT) to our document that one or more
   **    OLE objects may need to have their extents updated. later
   **    when this message is processed, the document loops through
   **    all lines to see if any are marked as needing an extent update.
   **    if infact the extents did change. this can be done by calling
   **    IViewObject2::GetExtent to retreive the object's current
   **    extents and comparing with the last known extents for the
   **    object. if the extents changed, then relayout space for the
   **    object before drawing. we postpone the check to get
   **    the extents now because OnViewChange is an asyncronis method,
   **    and we have to careful not to call any syncronis methods back
   **    to the object. while it WOULD be OK to call the
   **    IViewObject2::GetExtent method within the asyncronis
   **    OnViewChange method (because this method is handled by the
   **    object handler and never remoted), it is good practise to not
   **    call any object methods from within an asyncronis
   **    notification method.
   **    if there is already WM_U_UPDATEOBJECTEXTENT message waiting
   **    in our message queue, there is no need to post another one.
   **    in this way, if the server is updating quicker than we can
   **    keep up, we do not make unneccsary GetExtent calls. also if
   **    drawing is disabled, we postpone updating the extents of any
   **    objects until drawing is re-enabled.
   */
   lpContainerLine->m_fDoGetExtent = TRUE;
   hWndDoc = OutlineDoc_GetWindow((LPOUTLINEDOC)lpContainerLine->m_lpDoc);

   if (lpOutlineDoc->m_nDisableDraw == 0 &&
      ! PeekMessage(&msg, hWndDoc,
         WM_U_UPDATEOBJECTEXTENT, WM_U_UPDATEOBJECTEXTENT,
         PM_NOREMOVE | PM_NOYIELD)) {
      PostMessage(hWndDoc, WM_U_UPDATEOBJECTEXTENT, 0, 0L);
   }

   // force the modified line to redraw.
   lpLL = OutlineDoc_GetLineList(lpOutlineDoc);
   nIndex = LineList_GetLineIndex(lpLL, (LPLINE)lpContainerLine);
   LineList_ForceLineRedraw(lpLL, nIndex, TRUE /*fErase*/);

   OLEDBG_END2
   // release artificial AddRef
   CntrLine_AdvSink_Release(lpThis);
}


// IAdviseSink::OnRename
STDMETHODIMP_(void) CntrLine_AdvSink_OnRename(
      LPADVISESINK        lpThis,
      LPMONIKER           lpmk
)
{
   OleDbgOut2("CntrLine_AdvSink_OnRename\r\n");
   /* OLE2NOTE: the Embedding Container has nothing to do here. this
   **    notification is important for linking situations. it tells
   **    the OleLink objects to update their moniker because the
   **    source object has been renamed (track the link source).
   */
}


// IAdviseSink::OnSave
STDMETHODIMP_(void) CntrLine_AdvSink_OnSave(LPADVISESINK lpThis)
{
   OleDbgOut2("CntrLine_AdvSink_OnSave\r\n");
   /* OLE2NOTE: the Embedding Container has nothing to do here. this
   **    notification is only useful to clients which have set up a
   **    data cache with the ADVFCACHE_ONSAVE flag.
   */
}


// IAdviseSink::OnClose
STDMETHODIMP_(void) CntrLine_AdvSink_OnClose(LPADVISESINK lpThis)
{
   OleDbgOut2("CntrLine_AdvSink_OnClose\r\n");
   /* OLE2NOTE: the Embedding Container has nothing to do here. this
   **    notification is important for the OLE's default object handler
   **    and the OleLink object. it tells them the remote object is
   **    shutting down.
   */
}


/*************************************************************************
** ContainerLine Support Functions
*************************************************************************/


/* ContainerLine_Init
** ------------------
**  Initialize fields in a newly constructed ContainerLine line object.
**  NOTE: ref cnt of ContainerLine initialized to 0
*/
void ContainerLine_Init(LPCONTAINERLINE lpContainerLine, int nTab, HDC hDC)
{
   Line_Init((LPLINE)lpContainerLine, nTab, hDC);  // init base class fields

   ((LPLINE)lpContainerLine)->m_lineType           = CONTAINERLINETYPE;
   ((LPLINE)lpContainerLine)->m_nWidthInHimetric   = DEFOBJWIDTH;
   ((LPLINE)lpContainerLine)->m_nHeightInHimetric  = DEFOBJHEIGHT;
   lpContainerLine->m_cRef                         = 0;
   lpContainerLine->m_szStgName[0]                 = '\0';
   lpContainerLine->m_fObjWinOpen                  = FALSE;
   lpContainerLine->m_fMonikerAssigned             = FALSE;
   lpContainerLine->m_dwDrawAspect                 = DVASPECT_CONTENT;

   lpContainerLine->m_fGuardObj                    = FALSE;
   lpContainerLine->m_fDoGetExtent                 = FALSE;
   lpContainerLine->m_fDoSetExtent                 = FALSE;
   lpContainerLine->m_sizeInHimetric.cx            = -1;
   lpContainerLine->m_sizeInHimetric.cy            = -1;

   lpContainerLine->m_lpStg                        = NULL;
   lpContainerLine->m_lpDoc                        = NULL;
   lpContainerLine->m_lpOleObj                     = NULL;
   lpContainerLine->m_lpViewObj2                   = NULL;
   lpContainerLine->m_lpPersistStg                 = NULL;
   lpContainerLine->m_lpOleLink                    = NULL;
   lpContainerLine->m_dwLinkType                   = 0;
   lpContainerLine->m_fLinkUnavailable             = FALSE;
   lpContainerLine->m_lpszShortType                = NULL;

#if defined( INPLACE_CNTR )
   lpContainerLine->m_fIpActive                    = FALSE;
   lpContainerLine->m_fUIActive                    = FALSE;
   lpContainerLine->m_fIpVisible                   = FALSE;
   lpContainerLine->m_lpOleIPObj                   = NULL;
   lpContainerLine->m_fInsideOutObj                = FALSE;
   lpContainerLine->m_fIpChangesUndoable           = FALSE;
   lpContainerLine->m_fIpServerRunning             = FALSE;
   lpContainerLine->m_hWndIpObject                 = NULL;
   lpContainerLine->m_nHorizScrollShift            = 0;
#endif  // INPLACE_CNTR

   INIT_INTERFACEIMPL(
         &lpContainerLine->m_Unknown,
         &g_CntrLine_UnknownVtbl,
         lpContainerLine
   );

   INIT_INTERFACEIMPL(
         &lpContainerLine->m_OleClientSite,
         &g_CntrLine_OleClientSiteVtbl,
         lpContainerLine
   );

   INIT_INTERFACEIMPL(
         &lpContainerLine->m_AdviseSink,
         &g_CntrLine_AdviseSinkVtbl,
         lpContainerLine
   );

#if defined( INPLACE_CNTR )
   INIT_INTERFACEIMPL(
         &lpContainerLine->m_OleInPlaceSite,
         &g_CntrLine_OleInPlaceSiteVtbl,
         lpContainerLine
   );
#endif  // INPLACE_CNTR
}


/* Setup the OLE object associated with the ContainerLine */
BOOL ContainerLine_SetupOleObject(
      LPCONTAINERLINE         lpContainerLine,
      BOOL                    fDisplayAsIcon,
      HGLOBAL                 hMetaPict
)
{
   DWORD dwDrawAspect = (fDisplayAsIcon ? DVASPECT_ICON : DVASPECT_CONTENT);
   LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC)lpContainerLine->m_lpDoc;

   /* Cache a pointer to the IViewObject2* interface. *Required*
   **      we need this everytime we draw the object.
   **
   ** OLE2NOTE: We require the object to support IViewObject2
   **    interface. this is an extension to the IViewObject interface
   **    that was added with the OLE 2.01 release. This interface must
   **    be supported by all object handlers and DLL-based objects.
   */
   lpContainerLine->m_lpViewObj2 = (LPVIEWOBJECT2)OleStdQueryInterface(
         (LPUNKNOWN)lpContainerLine->m_lpOleObj, &IID_IViewObject2);
   if (! lpContainerLine->m_lpViewObj2) {
#if defined( _DEBUG )
      OleDbgAssertSz(
         lpContainerLine->m_lpViewObj2,"IViewObject2 NOT supported\r\n");
#endif
      return FALSE;
   }

   // Cache a pointer to the IPersistStorage* interface. *Required*
   //      we need this everytime we save the object.
   lpContainerLine->m_lpPersistStg = (LPPERSISTSTORAGE)OleStdQueryInterface(
         (LPUNKNOWN)lpContainerLine->m_lpOleObj, &IID_IPersistStorage);
   if (! lpContainerLine->m_lpPersistStg) {
      OleDbgAssert(lpContainerLine->m_lpPersistStg);
      return FALSE;
   }

   // Cache a pointer to the IOleLink* interface if supported. *Optional*
   //      if supported the object is a link. we need this to manage the link
   lpContainerLine->m_lpOleLink = (LPOLELINK)OleStdQueryInterface(
         (LPUNKNOWN)lpContainerLine->m_lpOleObj, &IID_IOleLink);
   if (lpContainerLine->m_lpOleLink) {
      OLEDBG_BEGIN2("IOleLink::GetUpdateOptions called\r\n")
      lpContainerLine->m_lpOleLink->lpVtbl->GetUpdateOptions(
            lpContainerLine->m_lpOleLink, &lpContainerLine->m_dwLinkType);
      OLEDBG_END2
   } else
      lpContainerLine->m_dwLinkType = 0;  // NOT a link

   /* get the short user type name of the object. this
   **    is used all the time when we have to build the object
   **    verb menu. we will cache this information to make it
   **    quicker to build the verb menu.
   */
   OleDbgAssert(lpContainerLine->m_lpszShortType == NULL);
   OLEDBG_BEGIN2("IOleObject::GetUserType called\r\n")
   lpContainerLine->m_lpOleObj->lpVtbl->GetUserType(
         lpContainerLine->m_lpOleObj,
         USERCLASSTYPE_SHORT,
         (LPOLESTR FAR*)&lpContainerLine->m_lpszShortType
   );
   OLEDBG_END2

   /* Perform that standard setup for the OLE object. this includes:
   **      setup View advise
   **      Call IOleObject::SetHostNames
   **      Call OleSetContainedObject
   */
   OleStdSetupAdvises(
         lpContainerLine->m_lpOleObj,
         dwDrawAspect,
         (LPOLESTR)APPNAME,
         lpOutlineDoc->m_lpszDocTitle,
         (LPADVISESINK)&lpContainerLine->m_AdviseSink,
         TRUE    /*fCreate*/
   );

#if defined( INPLACE_CNTR )
   /* OLE2NOTE: (INSIDE-OUT CONTAINER) An inside-out container should
   **    check if the object is an inside-out and prefers to be
   **    activated when visible type of object. if not the object
   **    should not be allowed to keep its window up after it gets
   **    UIDeactivated.
   */
   if (g_fInsideOutContainer) {
      DWORD mstat;
      OLEDBG_BEGIN2("IOleObject::GetMiscStatus called\r\n")
      lpContainerLine->m_lpOleObj->lpVtbl->GetMiscStatus(
            lpContainerLine->m_lpOleObj,
            DVASPECT_CONTENT,
            (DWORD FAR*)&mstat
      );
      OLEDBG_END2

      lpContainerLine->m_fInsideOutObj = (BOOL)
            (mstat & (OLEMISC_INSIDEOUT|OLEMISC_ACTIVATEWHENVISIBLE));
   }
#endif  // INPLACE_CNTR

   if (fDisplayAsIcon) {
      /* user has requested to display icon aspect instead of content
      **    aspect.
      **    NOTE: we do not have to delete the previous aspect cache
      **    because one did not get set up.
      */
      OleStdSwitchDisplayAspect(
            lpContainerLine->m_lpOleObj,
            &lpContainerLine->m_dwDrawAspect,
            dwDrawAspect,
            hMetaPict,
            FALSE,  /* fDeleteOldAspect */
            TRUE,   /* fSetupViewAdvise */
            (LPADVISESINK)&lpContainerLine->m_AdviseSink,
            NULL /*fMustUpdate*/        // this can be ignored; update
                                 // for switch to icon not req'd
      );
   }
   return TRUE;
}


/* Create an ContainerLine object and return the pointer */
LPCONTAINERLINE ContainerLine_Create(
      DWORD                   dwOleCreateType,
      HDC                     hDC,
      UINT                    nTab,
      LPCONTAINERDOC          lpContainerDoc,
      LPCLSID                 lpclsid,
      LPOLESTR                lpszFileName,
      BOOL                    fDisplayAsIcon,
      HGLOBAL                 hMetaPict,
      LPOLESTR                lpszStgName
)
{
   LPCONTAINERLINE lpContainerLine = NULL;
   LPOLEOBJECT     lpObj = NULL;
   LPSTORAGE       lpDocStg = ContainerDoc_GetStg(lpContainerDoc);
   DWORD           dwDrawAspect =
                  (fDisplayAsIcon ? DVASPECT_ICON : DVASPECT_CONTENT);
   DWORD           dwOleRenderOpt =
                  (fDisplayAsIcon ? OLERENDER_NONE : OLERENDER_DRAW);
   HRESULT         hrErr;

   OLEDBG_BEGIN3("ContainerLine_Create\r\n")

   if (lpDocStg == NULL) {
      OleDbgAssertSz(lpDocStg != NULL, "Doc storage is NULL");
      goto error;
   }

   lpContainerLine=(LPCONTAINERLINE) New((DWORD)sizeof(CONTAINERLINE));
   if (lpContainerLine == NULL) {
      OleDbgAssertSz(lpContainerLine!=NULL,"Error allocating ContainerLine");
      goto error;
   }

   ContainerLine_Init(lpContainerLine, nTab, hDC);

   /* OLE2NOTE: in order to avoid re-entrancy we will set a flag to
   **    guard our object. if this guard is set, then the object is
   **    not ready to have any OLE interface methods called. it is
   **    necessary to guard the object this way while it is being
   **    created or loaded.
   */
   lpContainerLine->m_fGuardObj = TRUE;

   /* OLE2NOTE: In order to have a stable ContainerLine object we must
   **    AddRef the object's refcnt. this will be later released when
   **    the ContainerLine is deleted.
   */
   ContainerLine_AddRef(lpContainerLine);

   OLESTRCPY(lpContainerLine->m_szStgName, lpszStgName);
   lpContainerLine->m_lpDoc = lpContainerDoc;

   /* Create a new storage for the object inside the doc's storage */
   lpContainerLine->m_lpStg = OleStdCreateChildStorage(lpDocStg,lpszStgName);
   if (lpContainerLine->m_lpStg == NULL) {
      OleDbgAssert(lpContainerLine->m_lpStg != NULL);
      goto error;
   }

   lpContainerLine->m_dwLinkType = 0;

   switch (dwOleCreateType) {

      case IOF_SELECTCREATENEW:

         OLEDBG_BEGIN2("OleCreate called\r\n")
         hrErr = OleCreate (
               lpclsid,
               &IID_IOleObject,
               dwOleRenderOpt,
               NULL,
               (LPOLECLIENTSITE)&lpContainerLine->m_OleClientSite,
               lpContainerLine->m_lpStg,
               (LPVOID FAR*)&lpContainerLine->m_lpOleObj
         );
         OLEDBG_END2

#if defined( _DEBUG )
         if (hrErr != NOERROR)
            OleDbgOutHResult("OleCreate returned", hrErr);
#endif

         break;

      case IOF_SELECTCREATEFROMFILE:

         OLEDBG_BEGIN2("OleCreateFromFile called\r\n")
         hrErr = OleCreateFromFile (
               &CLSID_NULL,
               lpszFileName,
               &IID_IOleObject,
               dwOleRenderOpt,
               NULL,
               (LPOLECLIENTSITE)&lpContainerLine->m_OleClientSite,
               lpContainerLine->m_lpStg,
               (LPVOID FAR*)&lpContainerLine->m_lpOleObj
         );
         OLEDBG_END2

#if defined( _DEBUG )
         if (hrErr != NOERROR)
            OleDbgOutHResult("OleCreateFromFile returned", hrErr);
#endif
         break;

      case IOF_CHECKLINK:

         OLEDBG_BEGIN2("OleCreateLinkToFile called\r\n")
         hrErr = OleCreateLinkToFile (
               lpszFileName,
               &IID_IOleObject,
               dwOleRenderOpt,
               NULL,
               (LPOLECLIENTSITE)&lpContainerLine->m_OleClientSite,
               lpContainerLine->m_lpStg,
               (LPVOID FAR*)&lpContainerLine->m_lpOleObj
         );
         OLEDBG_END2

#if defined( _DEBUG )
         if (hrErr != NOERROR)
            OleDbgOutHResult("OleCreateLinkToFile returned", hrErr);
#endif
         break;
   }
   if (hrErr != NOERROR)
      goto error;

   if (! ContainerLine_SetupOleObject(
                        lpContainerLine, fDisplayAsIcon, hMetaPict)) {
      goto error;
   }

   /* OLE2NOTE: clear our re-entrancy guard. the object is now ready
   **    to have interface methods called.
   */
   lpContainerLine->m_fGuardObj = FALSE;

   OLEDBG_END3
   return lpContainerLine;

error:
   OutlineApp_ErrorMessage(g_lpApp, OLESTR("Could not create object!"));

   // Destroy partially created OLE object
   if (lpContainerLine)
      ContainerLine_Delete(lpContainerLine);
   OLEDBG_END3
   return NULL;
}


LPCONTAINERLINE ContainerLine_CreateFromData(
      HDC                     hDC,
      UINT                    nTab,
      LPCONTAINERDOC          lpContainerDoc,
      LPDATAOBJECT            lpSrcDataObj,
      DWORD                   dwCreateType,
      CLIPFORMAT              cfFormat,
      BOOL                    fDisplayAsIcon,
      HGLOBAL                 hMetaPict,
      LPOLESTR                lpszStgName
)
{
   HGLOBAL         hData = NULL;
   LPCONTAINERLINE lpContainerLine = NULL;
   LPOLEOBJECT     lpObj = NULL;
   LPSTORAGE       lpDocStg = ContainerDoc_GetStg(lpContainerDoc);
   DWORD           dwDrawAspect =
                  (fDisplayAsIcon ? DVASPECT_ICON : DVASPECT_CONTENT);
   DWORD           dwOleRenderOpt;
   FORMATETC       renderFmtEtc;
   LPFORMATETC     lpRenderFmtEtc = NULL;
   HRESULT         hrErr;
   LPUNKNOWN       lpUnk = NULL;

   OLEDBG_BEGIN3("ContainerLine_CreateFromData\r\n")

   if (dwCreateType == OLECREATEFROMDATA_STATIC && cfFormat != 0) {
      // a particular type of static object should be created

      dwOleRenderOpt = OLERENDER_FORMAT;
      lpRenderFmtEtc = (LPFORMATETC)&renderFmtEtc;

      if (cfFormat == CF_METAFILEPICT)
         SETDEFAULTFORMATETC(renderFmtEtc, cfFormat, TYMED_MFPICT);
      else if (cfFormat == CF_BITMAP)
         SETDEFAULTFORMATETC(renderFmtEtc, cfFormat, TYMED_GDI);
      else
         SETDEFAULTFORMATETC(renderFmtEtc, cfFormat, TYMED_HGLOBAL);

   } else if (dwCreateType == OLECREATEFROMDATA_STATIC && fDisplayAsIcon) {
      // a link that currently displayed as an icon needs to be
      // converted to a STATIC picture object. this case is driven
      // from "BreakLink" in the "Links" dialog. because the current
      // data in the source object's cache is DVASPECT_ICON we need
      // to tell the OleCreateStaticFromData API to look for
      // DVASPECT_ICON data. the static object that results however,
      // is considered to be displayed in the DVASPECT_CONTENT view.

      dwOleRenderOpt = OLERENDER_DRAW;
      lpRenderFmtEtc = (LPFORMATETC)&renderFmtEtc;
      SETFORMATETC(renderFmtEtc,0,DVASPECT_ICON,NULL,TYMED_NULL,-1);
      dwDrawAspect = DVASPECT_CONTENT;   // static obj displays only CONTENT

   } else if (fDisplayAsIcon && hMetaPict) {
      // a special icon should be used. first we create the object
      // OLERENDER_NONE and then we stuff the special icon into the cache.

      dwOleRenderOpt = OLERENDER_NONE;

   } else if (fDisplayAsIcon && hMetaPict == NULL) {
      // the object's default icon should be used

      dwOleRenderOpt = OLERENDER_DRAW;
      lpRenderFmtEtc = (LPFORMATETC)&renderFmtEtc;
      SETFORMATETC(renderFmtEtc,0,DVASPECT_ICON,NULL,TYMED_NULL,-1);

   } else {
      // create standard DVASPECT_CONTENT/OLERENDER_DRAW object
      dwOleRenderOpt = OLERENDER_DRAW;
   }

   if (lpDocStg == NULL) {
      OleDbgAssertSz(lpDocStg != NULL, "Doc storage is NULL");
      goto error;
   }

   lpContainerLine=(LPCONTAINERLINE) New((DWORD)sizeof(CONTAINERLINE));
   if (lpContainerLine == NULL) {
      OleDbgAssertSz(lpContainerLine!=NULL,"Error allocating ContainerLine");
      goto error;
   }

   ContainerLine_Init(lpContainerLine, nTab, hDC);

   /* OLE2NOTE: in order to avoid re-entrancy we will set a flag to
   **    guard our object. if this guard is set, then the object is
   **    not ready to have any OLE interface methods called. it is
   **    necessary to guard the object this way while it is being
   **    created or loaded.
   */
   lpContainerLine->m_fGuardObj = TRUE;

   /* OLE2NOTE: In order to have a stable ContainerLine object we must
   **    AddRef the object's refcnt. this will be later released when
   **    the ContainerLine is deleted.
   */
   ContainerLine_AddRef(lpContainerLine);

   OLESTRCPY(lpContainerLine->m_szStgName, lpszStgName);
   lpContainerLine->m_lpDoc = lpContainerDoc;

   /* Create a new storage for the object inside the doc's storage */
   lpContainerLine->m_lpStg = OleStdCreateChildStorage(lpDocStg,lpszStgName);
   if (lpContainerLine->m_lpStg == NULL) {
      OleDbgAssert(lpContainerLine->m_lpStg != NULL);
      goto error;
   }

   switch (dwCreateType) {

      case OLECREATEFROMDATA_LINK:

         OLEDBG_BEGIN2("OleCreateLinkFromData called\r\n")
         hrErr = OleCreateLinkFromData (
               lpSrcDataObj,
               &IID_IOleObject,
               dwOleRenderOpt,
               lpRenderFmtEtc,
               (LPOLECLIENTSITE)&lpContainerLine->m_OleClientSite,
               lpContainerLine->m_lpStg,
               (LPVOID FAR*)&lpContainerLine->m_lpOleObj
         );
         OLEDBG_END2

#if defined( _DEBUG )
         if (hrErr != NOERROR)
            OleDbgOutHResult("OleCreateLinkFromData returned", hrErr);
#endif
         break;

      case OLECREATEFROMDATA_OBJECT:

         OLEDBG_BEGIN2("OleCreateFromData called\r\n")
         hrErr = OleCreateFromData (
               lpSrcDataObj,
               &IID_IOleObject,
               dwOleRenderOpt,
               lpRenderFmtEtc,
               (LPOLECLIENTSITE)&lpContainerLine->m_OleClientSite,
               lpContainerLine->m_lpStg,
               (LPVOID FAR*)&lpContainerLine->m_lpOleObj
         );
         OLEDBG_END2

#if defined( _DEBUG )
         if (hrErr != NOERROR)
            OleDbgOutHResult("OleCreateFromData returned", hrErr);
#endif
         break;

      case OLECREATEFROMDATA_STATIC:

         OLEDBG_BEGIN2("OleCreateStaticFromData called\r\n")
         hrErr = OleCreateStaticFromData (
               lpSrcDataObj,
               &IID_IOleObject,
               dwOleRenderOpt,
               lpRenderFmtEtc,
               (LPOLECLIENTSITE)&lpContainerLine->m_OleClientSite,
               lpContainerLine->m_lpStg,
               (LPVOID FAR*)&lpContainerLine->m_lpOleObj
         );
         OLEDBG_END2

#if defined( _DEBUG )
         if (hrErr != NOERROR)
            OleDbgOutHResult("OleCreateStaticFromData returned", hrErr);
#endif
         break;
   }

   if (hrErr != NOERROR)
      goto error;

   if (! ContainerLine_SetupOleObject(
                        lpContainerLine, fDisplayAsIcon, hMetaPict)) {
      goto error;
   }

   /* OLE2NOTE: clear our re-entrancy guard. the object is now ready
   **    to have interface methods called.
   */
   lpContainerLine->m_fGuardObj = FALSE;

   OLEDBG_END3
   return lpContainerLine;

error:
   OutlineApp_ErrorMessage(g_lpApp, OLESTR("Could not create object!"));
   // Destroy partially created OLE object
   if (lpContainerLine)
      ContainerLine_Delete(lpContainerLine);
   OLEDBG_END3
   return NULL;
}


/* ContainerLine_AddRef
** --------------------
**
**  increment the ref count of the line object.
**
**    Returns the new ref count on the object
*/
ULONG ContainerLine_AddRef(LPCONTAINERLINE lpContainerLine)
{
   ++lpContainerLine->m_cRef;

#if defined( _DEBUG )
   OleDbgOutRefCnt4(
         "ContainerLine_AddRef: cRef++\r\n",
         lpContainerLine,
         lpContainerLine->m_cRef
   );
#endif
   return lpContainerLine->m_cRef;
}


/* ContainerLine_Release
** ---------------------
**
**  decrement the ref count of the line object.
**    if the ref count goes to 0, then the line is destroyed.
**
**    Returns the remaining ref count on the object
*/
ULONG ContainerLine_Release(LPCONTAINERLINE lpContainerLine)
{
   ULONG cRef;

   /*********************************************************************
   ** OLE2NOTE: when the obj refcnt == 0, then destroy the object.     **
   **     otherwise the object is still in use.                        **
   *********************************************************************/

   cRef = --lpContainerLine->m_cRef;

#if defined( _DEBUG )
   OleDbgAssertSz(
         lpContainerLine->m_cRef >= 0,"Release called with cRef == 0");

   OleDbgOutRefCnt4(
         "ContainerLine_Release: cRef--\r\n",
         lpContainerLine,
         cRef
   );
#endif
   if (cRef == 0)
      ContainerLine_Destroy(lpContainerLine);

   return cRef;
}


/* ContainerLine_QueryInterface
** ----------------------------
**
** Retrieve a pointer to an interface on the ContainerLine object.
**
**    Returns NOERROR if interface is successfully retrieved.
**            E_NOINTERFACE if the interface is not supported
*/
HRESULT ContainerLine_QueryInterface(
      LPCONTAINERLINE         lpContainerLine,
      REFIID                  riid,
      LPVOID FAR*             lplpvObj
)
{
   SCODE sc = E_NOINTERFACE;

   /* OLE2NOTE: we must make sure to set all out ptr parameters to NULL. */
   *lplpvObj = NULL;

   if (IsEqualIID(riid, &IID_IUnknown)) {
      OleDbgOut4("ContainerLine_QueryInterface: IUnknown* RETURNED\r\n");

      *lplpvObj = (LPVOID) &lpContainerLine->m_Unknown;
      ContainerLine_AddRef(lpContainerLine);
      sc = S_OK;
   }
   else if (IsEqualIID(riid, &IID_IOleClientSite)) {
      OleDbgOut4("ContainerLine_QueryInterface: IOleClientSite* RETURNED\r\n");

      *lplpvObj = (LPVOID) &lpContainerLine->m_OleClientSite;
      ContainerLine_AddRef(lpContainerLine);
      sc = S_OK;
   }
   else if (IsEqualIID(riid, &IID_IAdviseSink)) {
      OleDbgOut4("ContainerLine_QueryInterface: IAdviseSink* RETURNED\r\n");

      *lplpvObj = (LPVOID) &lpContainerLine->m_AdviseSink;
      ContainerLine_AddRef(lpContainerLine);
      sc = S_OK;
   }
#if defined( INPLACE_CNTR )
   else if (IsEqualIID(riid, &IID_IOleWindow)
          || IsEqualIID(riid, &IID_IOleInPlaceSite)) {
      OleDbgOut4("ContainerLine_QueryInterface: IOleInPlaceSite* RETURNED\r\n");

      *lplpvObj = (LPVOID) &lpContainerLine->m_OleInPlaceSite;
      ContainerLine_AddRef(lpContainerLine);
      sc = S_OK;
   }
#endif  // INPLACE_CNTR

   OleDbgQueryInterfaceMethod(*lplpvObj);

   return ResultFromScode(sc);
}


BOOL ContainerLine_LoadOleObject(LPCONTAINERLINE lpContainerLine)
{
   LPOUTLINEDOC    lpOutlineDoc = (LPOUTLINEDOC)lpContainerLine->m_lpDoc;
   LPSTORAGE       lpDocStg = ContainerDoc_GetStg(lpContainerLine->m_lpDoc);
   LPOLECLIENTSITE lpOleClientSite;
   LPMONIKER       lpmkObj;
   LPOLEAPP        lpOleApp = (LPOLEAPP)g_lpApp;
   BOOL            fPrevEnable1;
   BOOL            fPrevEnable2;
   HRESULT         hrErr;

   if (lpContainerLine->m_fGuardObj)
      return FALSE;                // object in process of creation

   if (lpContainerLine->m_lpOleObj)
      return TRUE;                // object already loaded

   OLEDBG_BEGIN3("ContainerLine_LoadOleObject\r\n")

   /* OLE2NOTE: in order to avoid re-entrancy we will set a flag to
   **    guard our object. if this guard is set, then the object is
   **    not ready to have any OLE interface methods called. it is
   **    necessary to guard the object this way while it is being
   **    created or loaded.
   */
   lpContainerLine->m_fGuardObj = TRUE;

   /* if object storage is not already open, then open it */
   if (! lpContainerLine->m_lpStg) {
      lpContainerLine->m_lpStg = OleStdOpenChildStorage(
            lpDocStg,
            lpContainerLine->m_szStgName,
            STGM_READWRITE
      );
      if (lpContainerLine->m_lpStg == NULL) {
         OleDbgAssert(lpContainerLine->m_lpStg != NULL);
         goto error;
      }
   }

   /* OLE2NOTE: if the OLE object being loaded is in a data transfer
   **    document, then we should NOT pass a IOleClientSite* pointer
   **    to the OleLoad call. This particularly critical if the OLE
   **    object is an OleLink object. If a non-NULL client site is
   **    passed to the OleLoad function, then the link will bind to
   **    the source if its is running. in the situation that we are
   **    loading the object as part of a data transfer document we do
   **    not want this connection to be established. even worse, if
   **    the link source is currently blocked or busy, then this could
   **    hang the system. it is simplest to never pass a
   **    IOleClientSite* when loading an object in a data transfer
   **    document.
   */
   lpOleClientSite = (lpOutlineDoc->m_fDataTransferDoc ?
         NULL : (LPOLECLIENTSITE)&lpContainerLine->m_OleClientSite);

   /* OLE2NOTE: we do not want to ever give the Busy/NotResponding
   **    dialogs when we are loading an object. if the object is a
   **    link, it will attempt to BindIfRunning to the link source. if
   **    the link source is currently busy, this could cause the Busy
   **    dialog to come up. even if the link source is busy,
   **    we do not want put up the busy dialog. thus we will disable
   **    the dialog and later re-enable them
   */
   OleApp_DisableBusyDialogs(lpOleApp, &fPrevEnable1, &fPrevEnable2);

   OLEDBG_BEGIN2("OleLoad called\r\n")
   hrErr = OleLoad (
         lpContainerLine->m_lpStg,
         &IID_IOleObject,
         lpOleClientSite,
         (LPVOID FAR*)&lpContainerLine->m_lpOleObj
   );
   OLEDBG_END2

   // re-enable the Busy/NotResponding dialogs
   OleApp_EnableBusyDialogs(lpOleApp, fPrevEnable1, fPrevEnable2);

   if (hrErr != NOERROR) {
      OleDbgAssertSz(hrErr == NOERROR, "Could not load object!");
      OleDbgOutHResult("OleLoad returned", hrErr);
      goto error;
   }

   /* Cache a pointer to the IViewObject2* interface. *Required*
   **      we need this everytime we draw the object.
   **
   ** OLE2NOTE: We require the object to support IViewObject2
   **    interface. this is an extension to the IViewObject interface
   **    that was added with the OLE 2.01 release. This interface must
   **    be supported by all object handlers and DLL-based objects.
   */
   lpContainerLine->m_lpViewObj2 = (LPVIEWOBJECT2)OleStdQueryInterface(
         (LPUNKNOWN)lpContainerLine->m_lpOleObj, &IID_IViewObject2);
   if (! lpContainerLine->m_lpViewObj2) {
#if defined( _DEBUG )
      OleDbgAssertSz(
         lpContainerLine->m_lpViewObj2,"IViewObject2 NOT supported\r\n");
#endif
      goto error;
   }

   // Cache a pointer to the IPersistStorage* interface. *Required*
   //      we need this everytime we save the object.
   lpContainerLine->m_lpPersistStg = (LPPERSISTSTORAGE)OleStdQueryInterface(
         (LPUNKNOWN)lpContainerLine->m_lpOleObj, &IID_IPersistStorage);
   if (! lpContainerLine->m_lpPersistStg) {
      OleDbgAssert(lpContainerLine->m_lpPersistStg);
      goto error;
   }

   // Cache a pointer to the IOleLink* interface if supported. *Optional*
   //      if supported the object is a link. we need this to manage the link
   if (lpContainerLine->m_dwLinkType != 0) {
      lpContainerLine->m_lpOleLink = (LPOLELINK)OleStdQueryInterface(
            (LPUNKNOWN)lpContainerLine->m_lpOleObj, &IID_IOleLink);
      if (! lpContainerLine->m_lpOleLink) {
         OleDbgAssert(lpContainerLine->m_lpOleLink);
         goto error;
      }
   }

   /* OLE2NOTE: clear our re-entrancy guard. the object is now ready
   **    to have interface methods called.
   */
   lpContainerLine->m_fGuardObj = FALSE;

   /* OLE2NOTE: similarly, if the OLE object being loaded is in a data
   **    transfer document, then we do NOT need to setup any advises,
   **    call SetHostNames, SetMoniker, etc.
   */
   if (lpOleClientSite) {
      /* Setup the Advises (OLE notifications) that we are interested
      ** in receiving.
      */
      OleStdSetupAdvises(
            lpContainerLine->m_lpOleObj,
            lpContainerLine->m_dwDrawAspect,
            (LPOLESTR)APPNAME,
            lpOutlineDoc->m_lpszDocTitle,
            (LPADVISESINK)&lpContainerLine->m_AdviseSink,
            FALSE   /*fCreate*/
      );

      /* OLE2NOTE: if the OLE object has a moniker assigned, we need to
      **    inform the object by calling IOleObject::SetMoniker. this
      **    will force the OLE object to register in the
      **    RunningObjectTable when it enters the running state.
      */
      if (lpContainerLine->m_fMonikerAssigned) {
         lpmkObj = ContainerLine_GetRelMoniker(
               lpContainerLine,
               GETMONIKER_ONLYIFTHERE
         );

         if (lpmkObj) {
            OLEDBG_BEGIN2("IOleObject::SetMoniker called\r\n")
            lpContainerLine->m_lpOleObj->lpVtbl->SetMoniker(
                  lpContainerLine->m_lpOleObj,
                  OLEWHICHMK_OBJREL,
                  lpmkObj
            );
            OLEDBG_END2
            OleStdRelease((LPUNKNOWN)lpmkObj);
         }
      }

      /* get the Short form of the user type name of the object. this
      **    is used all the time when we have to build the object
      **    verb menu. we will cache this information to make it
      **    quicker to build the verb menu.
      */
      OLEDBG_BEGIN2("IOleObject::GetUserType called\r\n")
      lpContainerLine->m_lpOleObj->lpVtbl->GetUserType(
            lpContainerLine->m_lpOleObj,
            USERCLASSTYPE_SHORT,
            (LPOLESTR FAR*)&lpContainerLine->m_lpszShortType
      );
      OLEDBG_END2

#if defined( INPLACE_CNTR )
      /* OLE2NOTE: an inside-out container should check if the object
      **    is an inside-out and prefers to be activated when visible
      **    type of object. if so, the object should be immediately
      **    activated in-place, BUT NOT UIActived.
      */
      if (g_fInsideOutContainer &&
            lpContainerLine->m_dwDrawAspect == DVASPECT_CONTENT) {
         DWORD mstat;
         OLEDBG_BEGIN2("IOleObject::GetMiscStatus called\r\n")
         lpContainerLine->m_lpOleObj->lpVtbl->GetMiscStatus(
               lpContainerLine->m_lpOleObj,
               DVASPECT_CONTENT,
               (DWORD FAR*)&mstat
         );
         OLEDBG_END2

         lpContainerLine->m_fInsideOutObj = (BOOL)
               (mstat & (OLEMISC_INSIDEOUT|OLEMISC_ACTIVATEWHENVISIBLE));

         if ( lpContainerLine->m_fInsideOutObj ) {
            HWND hWndDoc = OutlineDoc_GetWindow(lpOutlineDoc);

            ContainerLine_DoVerb(
                  lpContainerLine,
                  OLEIVERB_INPLACEACTIVATE,
                  NULL,
                  FALSE,
                  FALSE
            );

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

   }

   OLEDBG_END2
   return TRUE;

error:
   OLEDBG_END2
   return FALSE;
}


/* ContainerLine_CloseOleObject
** ----------------------------
**    Close the OLE object associated with the ContainerLine.
**
**    Closing the object forces the object to transition from the
**    running state to the loaded state. if the object was not running,
**    then there is no effect. it is necessary to close the OLE object
**    before releasing the pointers to the OLE object.
**
**    Returns TRUE if successfully closed,
**            FALSE if closing was aborted.
*/
BOOL ContainerLine_CloseOleObject(
      LPCONTAINERLINE         lpContainerLine,
      DWORD                   dwSaveOption
)
{
   HRESULT hrErr;
   SCODE   sc;

   if (lpContainerLine->m_fGuardObj)
      return FALSE;                // object in process of creation

   if (! lpContainerLine->m_lpOleObj)
      return TRUE;    // object is NOT loaded

   OLEDBG_BEGIN2("IOleObject::Close called\r\n")
   hrErr = lpContainerLine->m_lpOleObj->lpVtbl->Close(
         lpContainerLine->m_lpOleObj,
         (dwSaveOption == OLECLOSE_NOSAVE ?
               OLECLOSE_NOSAVE : OLECLOSE_SAVEIFDIRTY)
   );
   OLEDBG_END2

#if defined( INPLACE_CNTR )
   if (lpContainerLine->m_fIpServerRunning) {
      /* OLE2NOTE: unlock the lock held on the in-place object.
      **    it is VERY important that an in-place container
      **    that also support linking to embeddings properly manage
      **    the running of its in-place objects. in an outside-in
      **    style in-place container, when the user clicks
      **    outside of the in-place active object, the object gets
      **    UIDeactivated and the object hides its window. in order
      **    to make the object fast to reactivate, the container
      **    deliberately does not call IOleObject::Close. the object
      **    stays running in the invisible unlocked state. the idea
      **    here is if the user simply clicks outside of the object
      **    and then wants to double click again to re-activate the
      **    object, we do not want this to be slow. if we want to
      **    keep the object running, however, we MUST Lock it
      **    running. otherwise the object will be in an unstable
      **    state where if a linking client does a "silent-update"
      **    (eg. UpdateNow from the Links dialog), then the in-place
      **    server will shut down even before the object has a chance
      **    to be saved back in its container. this saving normally
      **    occurs when the in-place container closes the object. also
      **    keeping the object in the unstable, hidden, running,
      **    not-locked state can cause problems in some scenarios.
      **    ICntrOtl keeps only one object running. if the user
      **    intiates a DoVerb on another object, then that last
      **    running in-place active object is closed. a more
      **    sophistocated in-place container may keep more object running.
      **    (see CntrLine_IPSite_OnInPlaceActivate)
      */
      lpContainerLine->m_fIpServerRunning = FALSE;

      OLEDBG_BEGIN2("OleLockRunning(FALSE,TRUE) called\r\n")
      OleLockRunning((LPUNKNOWN)lpContainerLine->m_lpOleObj, FALSE, TRUE);
      OLEDBG_END2
   }
#endif

   if (hrErr != NOERROR) {
      OleDbgOutHResult("IOleObject::Close returned", hrErr);
      sc = GetScode(hrErr);
      if (sc == RPC_E_CALL_REJECTED || sc==OLE_E_PROMPTSAVECANCELLED)
         return FALSE;   // object aborted shutdown
   }
   return TRUE;
}


/* ContainerLine_UnloadOleObject
** -----------------------------
**    Close the OLE object associated with the ContainerLine and
**    release all pointer held to the object.
**
**    Closing the object forces the object to transition from the
**    running state to the loaded state. if the object was not running,
**    then there is no effect. it is necessary to close the OLE object
**    before releasing the pointers to the OLE object. releasing all
**    pointers to the object allows the object to transition from
**    loaded to unloaded (or passive).
*/
void ContainerLine_UnloadOleObject(
      LPCONTAINERLINE         lpContainerLine,
      DWORD                   dwSaveOption
)
{
   if (lpContainerLine->m_lpOleObj) {

      OLEDBG_BEGIN2("IOleObject::Close called\r\n")
      lpContainerLine->m_lpOleObj->lpVtbl->Close(
            lpContainerLine->m_lpOleObj, dwSaveOption);
      OLEDBG_END2

      /* OLE2NOTE: we will take our IOleClientSite* pointer away from
      **    the object before we release all pointers to the object.
      **    in the scenario where the object is implemented as an
      **    in-proc server (DLL object), then, if there are link
      **    connections to the DLL object, it is possible that the
      **    object will not be destroyed when we release our pointers
      **    to the object. the existance of the remote link
      **    connections will hold the object alive. later when these
      **    strong connections are released, then the object may
      **    attempt to call IOleClientSite::Save if we had not taken
      **    away the client site pointer.
      */
      OLEDBG_BEGIN2("IOleObject::SetClientSite(NULL) called\r\n")
      lpContainerLine->m_lpOleObj->lpVtbl->SetClientSite(
            lpContainerLine->m_lpOleObj, NULL);
      OLEDBG_END2

      OleStdRelease((LPUNKNOWN)lpContainerLine->m_lpOleObj);
      lpContainerLine->m_lpOleObj = NULL;

      if (lpContainerLine->m_lpViewObj2) {
         OleStdRelease((LPUNKNOWN)lpContainerLine->m_lpViewObj2);
         lpContainerLine->m_lpViewObj2 = NULL;
      }
      if (lpContainerLine->m_lpPersistStg) {
         OleStdRelease((LPUNKNOWN)lpContainerLine->m_lpPersistStg);
         lpContainerLine->m_lpPersistStg = NULL;
      }

      if (lpContainerLine->m_lpOleLink) {
         OleStdRelease((LPUNKNOWN)lpContainerLine->m_lpOleLink);
         lpContainerLine->m_lpOleLink = NULL;
      }
   }

   if (lpContainerLine->m_lpszShortType) {
      OleStdFreeString(lpContainerLine->m_lpszShortType, NULL);
      lpContainerLine->m_lpszShortType = NULL;
   }
}


/* ContainerLine_Delete
** --------------------
**    Delete the ContainerLine.
**
**    NOTE: we can NOT directly destroy the memory for the
**    ContainerLine; the ContainerLine maintains a reference count. a
**    non-zero reference count indicates that the object is still
**    in-use. The OleObject keeps a reference-counted pointer to the
**    ClientLine object. we must take the actions necessary so that the
**    ContainerLine object receives Releases for outstanding
**    references. when the reference count of the ContainerLine reaches
**    zero, then the memory for the object will actually be destroyed
**    (ContainerLine_Destroy called).
**
*/
void ContainerLine_Delete(LPCONTAINERLINE lpContainerLine)
{
   OLEDBG_BEGIN2("ContainerLine_Delete\r\n")

#if defined( INPLACE_CNTR )
   if (lpContainerLine == lpContainerLine->m_lpDoc->m_lpLastIpActiveLine)
      lpContainerLine->m_lpDoc->m_lpLastIpActiveLine = NULL;
   if (lpContainerLine == lpContainerLine->m_lpDoc->m_lpLastUIActiveLine)
      lpContainerLine->m_lpDoc->m_lpLastUIActiveLine = NULL;
#endif

   /* OLE2NOTE: in order to have a stable line object during the
   **    process of deleting, we intially AddRef the line ref cnt and
   **    later Release it. This initial AddRef is artificial; it is
   **    simply done to guarantee that our object does not destroy
   **    itself until the END of this routine.
   */
   ContainerLine_AddRef(lpContainerLine);

   // Unload the loaded OLE object
   if (lpContainerLine->m_lpOleObj)
      ContainerLine_UnloadOleObject(lpContainerLine, OLECLOSE_NOSAVE);

   /* OLE2NOTE: we can NOT directly free the memory for the ContainerLine
   **    data structure until everyone holding on to a pointer to our
   **    ClientSite interface and IAdviseSink interface has released
   **    their pointers. There is one refcnt on the ContainerLine object
   **    which is held by the container itself. we will release this
   **    refcnt here.
   */
   ContainerLine_Release(lpContainerLine);

   /* OLE2NOTE: this call forces all external connections to our
   **    ContainerLine to close down and therefore guarantees that
   **    we receive all releases associated with those external
   **    connections. Strictly this call should NOT be necessary, but
   **    it is defensive coding to make this call.
   */
   OLEDBG_BEGIN2("CoDisconnectObject(lpContainerLine) called\r\n")
   CoDisconnectObject((LPUNKNOWN)&lpContainerLine->m_Unknown, 0);
   OLEDBG_END2

#if defined( _DEBUG )
   /* at this point the object all references from the OLE object to
   **    our ContainerLine object should have been released. there
   **    should only be 1 remaining reference that will be released below.
   */
   if (lpContainerLine->m_cRef != 1) {
      OleDbgOutRefCnt(
         "WARNING: ContainerLine_Delete: cRef != 1\r\n",
         lpContainerLine,
         lpContainerLine->m_cRef
      );
   }
#endif

   ContainerLine_Release(lpContainerLine); // release artificial AddRef above
   OLEDBG_END2
}


/* ContainerLine_Destroy
** ---------------------
**    Destroy (Free) the memory used by a ContainerLine structure.
**    This function is called when the ref count of the ContainerLine goes
**    to zero. the ref cnt goes to zero after ContainerLine_Delete forces
**    the OleObject to unload and release its pointers to the
**    ContainerLine IOleClientSite and IAdviseSink interfaces.
*/

void ContainerLine_Destroy(LPCONTAINERLINE lpContainerLine)
{
   LPUNKNOWN lpTmpObj;

   OLEDBG_BEGIN2("ContainerLine_Destroy\r\n")

   // Release the storage opened for the OLE object
   if (lpContainerLine->m_lpStg) {
      lpTmpObj = (LPUNKNOWN)lpContainerLine->m_lpStg;
      lpContainerLine->m_lpStg = NULL;

      OleStdRelease(lpTmpObj);
   }

   if (lpContainerLine->m_lpszShortType) {
      OleStdFreeString(lpContainerLine->m_lpszShortType, NULL);
      lpContainerLine->m_lpszShortType = NULL;
   }

   Delete(lpContainerLine);        // Free the memory for the structure itself
   OLEDBG_END2
}


/* ContainerLine_CopyToDoc
 * -----------------------
 *
 *      Copy a ContainerLine to another Document (usually ClipboardDoc)
 */
BOOL ContainerLine_CopyToDoc(
      LPCONTAINERLINE         lpSrcLine,
      LPOUTLINEDOC            lpDestDoc,
      int                     nIndex
)
{
   LPCONTAINERLINE lpDestLine = NULL;
   LPLINELIST  lpDestLL = &lpDestDoc->m_LineList;
   HDC         hDC;
   HRESULT     hrErr;
   BOOL        fStatus;
   LPSTORAGE   lpDestDocStg = ((LPOLEDOC)lpDestDoc)->m_lpStg;
   LPSTORAGE   lpDestObjStg = NULL;

   lpDestLine = (LPCONTAINERLINE) New((DWORD)sizeof(CONTAINERLINE));
   if (lpDestLine == NULL) {
      OleDbgAssertSz(lpDestLine!=NULL, "Error allocating ContainerLine");
      return FALSE;
   }

   hDC = LineList_GetDC(lpDestLL);
   ContainerLine_Init(lpDestLine, ((LPLINE)lpSrcLine)->m_nTabLevel, hDC);
   LineList_ReleaseDC(lpDestLL, hDC);

   /* OLE2NOTE: In order to have a stable ContainerLine object we must
   **    AddRef the object's refcnt. this will be later released when
   **    the ContainerLine is deleted.
   */
   ContainerLine_AddRef(lpDestLine);

   lpDestLine->m_lpDoc = (LPCONTAINERDOC)lpDestDoc;

   // Copy data of the original source ContainerLine.
   ((LPLINE)lpDestLine)->m_nWidthInHimetric =
         ((LPLINE)lpSrcLine)->m_nWidthInHimetric;
   ((LPLINE)lpDestLine)->m_nHeightInHimetric =
         ((LPLINE)lpSrcLine)->m_nHeightInHimetric;
   lpDestLine->m_fMonikerAssigned = lpSrcLine->m_fMonikerAssigned;
   lpDestLine->m_dwDrawAspect = lpSrcLine->m_dwDrawAspect;
   lpDestLine->m_sizeInHimetric = lpSrcLine->m_sizeInHimetric;
   lpDestLine->m_dwLinkType = lpSrcLine->m_dwLinkType;


   /* We must create a new sub-storage for the embedded object within
   **    the destination document's storage. We will first attempt to
   **    use the same storage name as the source line. if this name is
   **    in use, then we will allocate a new name. in this way we try
   **    to keep the name associated with the OLE object unchanged
   **    through a Cut/Paste operation.
   */
   lpDestObjStg = OleStdCreateChildStorage(
         lpDestDocStg,
         lpSrcLine->m_szStgName
   );
   if (lpDestObjStg) {
      OLESTRCPY(lpDestLine->m_szStgName, lpSrcLine->m_szStgName);
   } else {
      /* the original name was in use, make up a new name. */
      ContainerDoc_GetNextStgName(
            (LPCONTAINERDOC)lpDestDoc,
            lpDestLine->m_szStgName,
            OLESTRLEN(lpDestLine->m_szStgName)
      );
      lpDestObjStg = OleStdCreateChildStorage(
            lpDestDocStg,
            lpDestLine->m_szStgName
      );
   }
   if (lpDestObjStg == NULL) {
      OleDbgAssertSz(lpDestObjStg != NULL, "Error creating child stg");
      goto error;
   }

   // Copy over storage of the embedded object itself

   if (! lpSrcLine->m_lpOleObj) {

      /*****************************************************************
      ** CASE 1: object is NOT loaded.
      **    because the object is not loaded, we can simply copy the
      **    object's current storage to the new storage.
      *****************************************************************/

      /* if current object storage is not already open, then open it */
      if (! lpSrcLine->m_lpStg) {
         LPSTORAGE lpSrcDocStg = ((LPOLEDOC)lpSrcLine->m_lpDoc)->m_lpStg;

         if (! lpSrcDocStg) goto error;

         // open object storage.
         lpSrcLine->m_lpStg = OleStdOpenChildStorage(
               lpSrcDocStg,
               lpSrcLine->m_szStgName,
               STGM_READWRITE
         );
         if (lpSrcLine->m_lpStg == NULL) {
#if defined( _DEBUG )
            OleDbgAssertSz(
                  lpSrcLine->m_lpStg != NULL,
                  "Error opening child stg"
            );
#endif
            goto error;
         }
      }

      hrErr = lpSrcLine->m_lpStg->lpVtbl->CopyTo(
            lpSrcLine->m_lpStg,
            0,
            NULL,
            NULL,
            lpDestObjStg
      );
      if (hrErr != NOERROR) {
         OleDbgOutHResult("WARNING: lpSrcObjStg->CopyTo returned", hrErr);
         goto error;
      }

      fStatus = OleStdCommitStorage(lpDestObjStg);

   } else {

      /*****************************************************************
      ** CASE 2: object IS loaded.
      **    we must tell the object to save into the new storage.
      *****************************************************************/

      SCODE sc = S_OK;
      LPPERSISTSTORAGE lpPersistStg = lpSrcLine->m_lpPersistStg;
      OleDbgAssert(lpPersistStg);

      OLEDBG_BEGIN2("OleSave called\r\n")
      hrErr = OleSave(lpPersistStg, lpDestObjStg, FALSE /*fSameAsLoad*/);
      OLEDBG_END2

      if (hrErr != NOERROR) {
         OleDbgOutHResult("WARNING: OleSave returned", hrErr);
         sc = GetScode(hrErr);
      }

      // OLE2NOTE: even if OleSave fails, SaveCompleted must be called.
      OLEDBG_BEGIN2("IPersistStorage::SaveCompleted called\r\n")
      hrErr=lpPersistStg->lpVtbl->SaveCompleted(lpPersistStg,NULL);
      OLEDBG_END2

      if (hrErr != NOERROR) {
         OleDbgOutHResult("WARNING: SaveCompleted returned",hrErr);
         if (sc == S_OK)
            sc = GetScode(hrErr);
      }

      if (sc != S_OK)
         goto error;

   }

   OutlineDoc_AddLine(lpDestDoc, (LPLINE)lpDestLine, nIndex);
   OleStdVerifyRelease(
         (LPUNKNOWN)lpDestObjStg,
         OLESTR("Copied object stg not released")
   );

   return TRUE;

error:

   // Delete any partially created storage.
   if (lpDestObjStg) {
      OleStdVerifyRelease(
            (LPUNKNOWN)lpDestObjStg,
            OLESTR("Copied object stg not released")
      );

      lpDestDocStg->lpVtbl->DestroyElement(
            lpDestDocStg,
            lpDestLine->m_szStgName
      );
      lpDestLine->m_szStgName[0] = '\0';
   }

   // destroy partially created ContainerLine
   if (lpDestLine)
      ContainerLine_Delete(lpDestLine);
   return FALSE;
}


/* ContainerLine_UpdateExtent
** --------------------------
**   Update the size of the ContainerLine because the extents of the
**    object may have changed.
**
**    NOTE: because we are using a Windows OwnerDraw ListBox, we must
**    constrain the maximum possible height of a line. the ListBox has
**    a limitation (unfortunately) that no line can become larger than
**    255 pixels. thus we force the object to scale maintaining its
**    aspect ratio if this maximum line height limit is reached. the
**    actual maximum size for an object at 100% Zoom is
**    255
**
**    RETURNS TRUE -- if the extents of the object changed
**            FALSE -- if the extents did NOT change
*/
BOOL ContainerLine_UpdateExtent(
      LPCONTAINERLINE     lpContainerLine,
      LPSIZEL             lpsizelHim
)
{
   LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC)lpContainerLine->m_lpDoc;
   LPLINELIST lpLL = OutlineDoc_GetLineList(lpOutlineDoc);
   LPLINE lpLine = (LPLINE)lpContainerLine;
   int nIndex = LineList_GetLineIndex(lpLL, lpLine);
   UINT nOrgWidthInHimetric = lpLine->m_nWidthInHimetric;
   UINT nOrgHeightInHimetric = lpLine->m_nHeightInHimetric;
   BOOL fWidthChanged = FALSE;
   BOOL fHeightChanged = FALSE;
   SIZEL sizelHim;
   HRESULT hrErr;

   if (!lpContainerLine || !lpContainerLine->m_lpOleObj)
      return FALSE;

   if (lpContainerLine->m_fGuardObj)
      return FALSE;                // object in process of creation

   OLEDBG_BEGIN3("ContainerLine_UpdateExtent\r\n");

   lpContainerLine->m_fDoGetExtent = FALSE;

   if (! lpsizelHim) {
      /* OLE2NOTE: We want to call IViewObject2::GetExtent instead of
      **    IOleObject::GetExtent. IViewObject2::GetExtent method was
      **    added in OLE 2.01 release. It always retrieves the
      **    extents of the object corresponding to that which will be
      **    drawn by calling IViewObject::Draw. Normally, this is
      **    determined by the data stored in the data cache. This
      **    call will never result in a remoted (LRPC) call.
      */
      OLEDBG_BEGIN2("IViewObject2::GetExtent called\r\n")
      hrErr = lpContainerLine->m_lpViewObj2->lpVtbl->GetExtent(
            lpContainerLine->m_lpViewObj2,
            lpContainerLine->m_dwDrawAspect,
            -1,     /*lindex*/
            NULL,   /*ptd*/
            (LPSIZEL)&sizelHim
      );
      OLEDBG_END2
      if (hrErr != NOERROR)
         sizelHim.cx = sizelHim.cy = 0;

      lpsizelHim = (LPSIZEL)&sizelHim;
   }

   if (lpsizelHim->cx == lpContainerLine->m_sizeInHimetric.cx &&
      lpsizelHim->cy == lpContainerLine->m_sizeInHimetric.cy) {
      goto noupdate;
   }

   if (lpsizelHim->cx > 0 || lpsizelHim->cy > 0) {
      lpContainerLine->m_sizeInHimetric = *lpsizelHim;
   } else {
      /* object does not have any extents; let's use our container
      **    chosen arbitrary size for OLE objects.
      */
      lpContainerLine->m_sizeInHimetric.cx = (long)DEFOBJWIDTH;
      lpContainerLine->m_sizeInHimetric.cy = (long)DEFOBJHEIGHT;
   }

   ContainerLine_SetLineHeightFromObjectExtent(
         lpContainerLine,
         (LPSIZEL)&lpContainerLine->m_sizeInHimetric);

   // if height of object changed, then reset the height of line in LineList
   if (nOrgHeightInHimetric != lpLine->m_nHeightInHimetric) {
      LineList_SetLineHeight(lpLL, nIndex, lpLine->m_nHeightInHimetric);
      fHeightChanged = TRUE;
   }

   fWidthChanged = LineList_RecalcMaxLineWidthInHimetric(
         lpLL,
         nOrgWidthInHimetric
   );
   fWidthChanged |= (nOrgWidthInHimetric != lpLine->m_nWidthInHimetric);

   if (fHeightChanged || fWidthChanged) {
      OutlineDoc_ForceRedraw(lpOutlineDoc, TRUE);

      // mark ContainerDoc as now dirty
      OutlineDoc_SetModified(lpOutlineDoc, TRUE, TRUE, TRUE);
   }

   OLEDBG_END3
   return TRUE;

noupdate:
   OLEDBG_END3
   return FALSE;   // No UPDATE necessary
}


/* ContainerLine_DoVerb
** --------------------
**    Activate the OLE object and perform a specific verb.
*/
BOOL ContainerLine_DoVerb(
      LPCONTAINERLINE lpContainerLine,
      LONG            iVerb,
      LPMSG           lpMsg,
      BOOL            fMessage,
      BOOL            fAction
)
{
   HRESULT hrErr;
   LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC)lpContainerLine->m_lpDoc;
   RECT rcPosRect;
   OLEDBG_BEGIN3("ContainerLine_DoVerb\r\n")

   if (lpContainerLine->m_fGuardObj) {
      // object in process of creation--Fail the DoVerb call
      hrErr = ResultFromScode(E_FAIL);
      goto error;
   }

   /* if object is not already loaded, then load it now. objects are
   **    loaded lazily in this manner.
   */
   if (! lpContainerLine->m_lpOleObj)
      ContainerLine_LoadOleObject(lpContainerLine);

   if (! lpContainerLine->m_lpOleObj) {
#if defined( _DEBUG )
      OleDbgAssertSz(
            lpContainerLine->m_lpOleObj != NULL,
            "OLE object not loaded"
      );
#endif
      goto error;
   }

ExecuteDoVerb:

   ContainerLine_GetPosRect(lpContainerLine, (LPRECT)&rcPosRect);

   // run the object
   hrErr = ContainerLine_RunOleObject(lpContainerLine);
   if (hrErr != NOERROR)
      goto error;

   /* Tell object server to perform a "verb". */
   OLEDBG_BEGIN2("IOleObject::DoVerb called\r\n")
   hrErr = lpContainerLine->m_lpOleObj->lpVtbl->DoVerb (
         lpContainerLine->m_lpOleObj,
         iVerb,
         lpMsg,
         (LPOLECLIENTSITE)&lpContainerLine->m_OleClientSite,
         -1,
         OutlineDoc_GetWindow(lpOutlineDoc),
         (LPCRECT)&rcPosRect
   );
   OLEDBG_END2

   /* OLE2NOTE: IOleObject::DoVerb may return a success code
   **    OLE_S_INVALIDVERB. this SCODE should NOT be considered an
   **    error; thus it is important to use the "FAILED" macro to
   **    check for an error SCODE.
   */
   if (FAILED(GetScode(hrErr))) {
      OleDbgOutHResult("WARNING: lpOleObj->DoVerb returned", hrErr);
      goto error;
   }

#if defined( INPLACE_CNTR )
   /* OLE2NOTE: we want to keep only 1 inplace server active at any
   **    given time. so when we start to do a DoVerb on another line,
   **    then we want to shut down the previously activated server. in
   **    this way we keep at most one inplace server active at a time.
   **    because it is possible that the object we want to do DoVerb
   **    on is handled by the same EXE as that of the previously
   **    activated server, then we do not want the EXE to be shut down
   **    only to be launched again. in order to avoid this we will do
   **    the DoVerb BEFORE trying to shutdown the previous object.
   */
   if (!g_fInsideOutContainer) {
      ContainerDoc_ShutDownLastInPlaceServerIfNotNeeded(
            lpContainerLine->m_lpDoc, lpContainerLine);
   }
#endif  // INPLACE_CNTR

   OLEDBG_END3
   return TRUE;

error:

   if (lpContainerLine->m_dwLinkType != 0)
      lpContainerLine->m_fLinkUnavailable = TRUE;

#if defined( INPLACE_CNTR )
   /* OLE2NOTE: we want to keep only 1 inplace server active at any
   **    given time. so when we start to do a DoVerb on another line,
   **    then we want to shut down the previously activated server. in
   **    this way we keep at most one inplace server active at a time.
   **    even though the DoVerb failed, we will still shutdown the
   **    previous server. it is possible that we ran out of memory and
   **    that the DoVerb will succeed next time after shutting down
   **    the pervious server.
   */
   if (!g_fInsideOutContainer) {
      ContainerDoc_ShutDownLastInPlaceServerIfNotNeeded(
            lpContainerLine->m_lpDoc, lpContainerLine);
   }
#endif  // INPLACE_CNTR

   /* OLE2NOTE: if an error occurs we must give the appropriate error
   **    message box. there are many potential errors that can occur.
   **    the OLE2.0 user model has specific guidelines as to the
   **    dialogs that should be displayed given the various potential
   **    errors (eg. server not registered, unavailable link source.
   **    the OLE2UI library includes support for most of the
   **    recommended message dialogs. (see OleUIPrompUser function)
   */
   if (fMessage) {
      BOOL fReDoVerb = ContainerLine_ProcessOleRunError(
            lpContainerLine,
            hrErr,
            fAction,
            (lpMsg==NULL && iVerb>=0)   /* fMenuInvoked */
      );
      if (fReDoVerb) {
         goto ExecuteDoVerb;
      }
   }

   OLEDBG_END3
   return FALSE;
}



/* ContainerLine_ProcessOleRunError
 * --------------------------------
 *
 *  Handle the various errors possible when attempting OleRun of an object.
 *  Popup up appropriate message according to the error and/or take action
 *  specified button pressed by the user.
 *
 *  OLE2NOTE: The OLE 2.0 User Interface Guidelines specify the messages
 *            that should be given for the following situations:
 *                  1. Link Source Unavailable...goto Links dialog
 *                  2. Server Not Registered...goto Convert dialog
 *                  3. Link Type Changed
 *                  4. Server Not Found
 *
 *  Returns:    TRUE -- repeat IOleObject::DoVerb call.
 *              FALSE -- do NOT repeat IOleObject::DoVerb call.
 *
 *  Comments:
 *      (see LinkTypeChanged case)
 */
BOOL ContainerLine_ProcessOleRunError(
      LPCONTAINERLINE         lpContainerLine,
      HRESULT                 hrErr,
      BOOL                    fAction,
      BOOL                    fMenuInvoked
)
{
   LPOUTLINEDOC    lpOutlineDoc = (LPOUTLINEDOC)lpContainerLine->m_lpDoc;
   HWND            hwndParent = OutlineDoc_GetWindow(lpOutlineDoc);
   SCODE           sc = GetScode(hrErr);
   BOOL            fReDoVerb = FALSE;
   char szTemp[256];
   char szTemp2[256];

   OleDbgOutHResult("ProcessError", hrErr);
   if ((sc >= MK_E_FIRST) && (sc <= MK_E_LAST))
      goto LinkSourceUnavailable;
   if (sc == OLE_E_CANT_BINDTOSOURCE)
      goto LinkSourceUnavailable;
   if (sc == STG_E_PATHNOTFOUND)
      goto LinkSourceUnavailable;
   if (sc == REGDB_E_CLASSNOTREG)
      goto ServerNotReg;
   if (sc == OLE_E_STATIC)
      goto ServerNotReg;  // user dblclk'ed a static object w/ no svr reg'd
   if (sc == OLE_E_CLASSDIFF)
      goto LinkTypeChanged;
   if (sc == CO_E_APPDIDNTREG)
      goto ServerNotFound;
   if (sc == CO_E_APPNOTFOUND)
      goto ServerNotFound;
   if (sc == E_OUTOFMEMORY)
      goto OutOfMemory;

   if (ContainerLine_IsOleLink(lpContainerLine))
      goto LinkSourceUnavailable;
   else
      goto ServerNotFound;


/*************************************************************************
** Error handling routines                                              **
*************************************************************************/
LinkSourceUnavailable:
   wcstombs(szTemp, APPNAME, 255);
   if (ID_PU_LINKS == OleUIPromptUser(
            (WORD)IDD_LINKSOURCEUNAVAILABLE,
            hwndParent,
            szTemp)) {
      if (fAction) {
         ContainerDoc_EditLinksCommand(lpContainerLine->m_lpDoc);
      }
   }
   return fReDoVerb;

ServerNotReg:
   {
   LPOLESTR lpszUserType = NULL;
   CLIPFORMAT  cfFormat;       // not used

   hrErr = ReadFmtUserTypeStg(
         lpContainerLine->m_lpStg, &cfFormat, &lpszUserType);

   wcstombs(szTemp, APPNAME, 255);
   wcstombs(szTemp2, lpszUserType, 255);
   if (ID_PU_CONVERT == OleUIPromptUser(
         (WORD)IDD_SERVERNOTREG,
         hwndParent,
         szTemp,
         (hrErr == NOERROR) ? szTemp2 : "Unknown Object")) {
      if (fAction) {
         ContainerDoc_ConvertCommand(
               lpContainerLine->m_lpDoc,
               TRUE        // fMustActivate
         );
      }
   }

   if (lpszUserType)
      OleStdFreeString(lpszUserType, NULL);

   return fReDoVerb;
   }


LinkTypeChanged:
   {
   /* OLE2NOTE: If IOleObject::DoVerb is executed on a Link object and it
   **    returns OLE_E_CLASSDIFF because the link source is no longer
   **    the expected class, then if the verb is a semantically
   **    defined verb (eg. OLEIVERB_PRIMARY, OLEIVERB_SHOW,
   **    OLEIVERB_OPEN, etc.), then the link should be re-created with
   **    the new link source and the same verb executed on the new
   **    link. there is no need to give a message to the user. if the
   **    user had selected a verb from the object's verb menu
   **    (fMenuInvoked==TRUE), then we can not be certain of the
   **    semantics of the verb and whether the new link can still
   **    support the verb. in this case the user is given a prompt
   **    telling him to "choose a different command offered by the new
   **    type".
   */

   LPOLESTR       lpszUserType = NULL;

   if (fMenuInvoked) {
      hrErr = lpContainerLine->m_lpOleObj->lpVtbl->GetUserType(
            lpContainerLine->m_lpOleObj,USERCLASSTYPE_FULL,&lpszUserType);
      wcstombs(szTemp, APPNAME, 255);
      wcstombs(szTemp2, lpszUserType, 255);
      OleUIPromptUser(
            (WORD)IDD_LINKTYPECHANGED,
            hwndParent,
            szTemp,
            (hrErr == NOERROR) ? szTemp2 : "Unknown Object"
      );
   } else {
      fReDoVerb = TRUE;
   }
   ContainerLine_ReCreateLinkBecauseClassDiff(lpContainerLine);

   if (lpszUserType)
      OleStdFreeString(lpszUserType, NULL);

   return fReDoVerb;
   }

ServerNotFound:
   wcstombs(szTemp, APPNAME, 255);
   OleUIPromptUser(
         (WORD)IDD_SERVERNOTFOUND,
         hwndParent,
         (LPOLESTR)szTemp);
   return fReDoVerb;

OutOfMemory:
   wcstombs(szTemp, APPNAME, 255);
   OleUIPromptUser(
         (WORD)IDD_OUTOFMEMORY,
         hwndParent,
         szTemp);
   return fReDoVerb;
}


/* ContainerLine_ReCreateLinkBecauseClassDiff
** ------------------------------------------
**    Re-create the link. The existing link was created when
**    the moniker binds to a link source bound of a different class
**    than the same moniker currently binds to. the link may be a
**    special link object specifically used with the old link
**    source class. thus the link object needs to be re-created to
**    give the new link source the opportunity to create its own
**    special link object. (see description "Custom Link Source")
*/
HRESULT ContainerLine_ReCreateLinkBecauseClassDiff(
      LPCONTAINERLINE lpContainerLine
)
{
   LPOLELINK   lpOleLink = lpContainerLine->m_lpOleLink;
   HGLOBAL     hMetaPict = NULL;
   LPMONIKER   lpmkLinkSrc = NULL;
   SCODE       sc = E_FAIL;
   HRESULT     hrErr;

   if (lpOleLink &&
      lpOleLink->lpVtbl->GetSourceMoniker(
            lpOleLink, (LPMONIKER FAR*)&lpmkLinkSrc) == NOERROR) {

      BOOL            fDisplayAsIcon =
                     (lpContainerLine->m_dwDrawAspect==DVASPECT_ICON);
      STGMEDIUM       medium;
      LPDATAOBJECT    lpDataObj = NULL;
      DWORD           dwOleRenderOpt;
      FORMATETC       renderFmtEtc;
      LPFORMATETC     lpRenderFmtEtc = NULL;

      // get the current icon if object is displayed as icon
      if (fDisplayAsIcon &&
         (lpDataObj = (LPDATAOBJECT)OleStdQueryInterface( (LPUNKNOWN)
               lpContainerLine->m_lpOleObj,&IID_IDataObject)) != NULL ) {
         hMetaPict = OleStdGetData(
               lpDataObj, CF_METAFILEPICT, NULL, DVASPECT_ICON, &medium);
         OleStdRelease((LPUNKNOWN)lpDataObj);
      }

      if (fDisplayAsIcon && hMetaPict) {
         // a special icon should be used. first we create the object
         // OLERENDER_NONE. then we stuff the special icon into the cache.

         dwOleRenderOpt = OLERENDER_NONE;

      } else if (fDisplayAsIcon && hMetaPict == NULL) {
         // the object's default icon should be used

         dwOleRenderOpt = OLERENDER_DRAW;
         lpRenderFmtEtc = (LPFORMATETC)&renderFmtEtc;
         SETFORMATETC(renderFmtEtc,0,DVASPECT_ICON,NULL,TYMED_NULL,-1);

      } else {
         // create standard DVASPECT_CONTENT/OLERENDER_DRAW object
         dwOleRenderOpt = OLERENDER_DRAW;
      }

      // unload original link object
      ContainerLine_UnloadOleObject(lpContainerLine, OLECLOSE_SAVEIFDIRTY);

      // delete entire contents of the current object's storage
      OleStdDestroyAllElements(lpContainerLine->m_lpStg);

      OLEDBG_BEGIN2("OleCreateLink called\r\n")
      hrErr = OleCreateLink (
            lpmkLinkSrc,
            &IID_IOleObject,
            dwOleRenderOpt,
            lpRenderFmtEtc,
            (LPOLECLIENTSITE)&lpContainerLine->m_OleClientSite,
            lpContainerLine->m_lpStg,
            (LPVOID FAR*)&lpContainerLine->m_lpOleObj
      );
      OLEDBG_END2

      if (hrErr == NOERROR) {
         if (! ContainerLine_SetupOleObject(
               lpContainerLine, fDisplayAsIcon, hMetaPict) ) {

            // ERROR: setup of the new link failed.
            // revert the storage to restore the original link.
            ContainerLine_UnloadOleObject(
                  lpContainerLine, OLECLOSE_NOSAVE);
            lpContainerLine->m_lpStg->lpVtbl->Revert(
                  lpContainerLine->m_lpStg);
            sc = E_FAIL;
         } else {
            sc = S_OK;  // IT WORKED!

         }
      }
      else {
         sc = GetScode(hrErr);
         OleDbgOutHResult("OleCreateLink returned", hrErr);
         // ERROR: Re-creating the link failed.
         // revert the storage to restore the original link.
         lpContainerLine->m_lpStg->lpVtbl->Revert(
               lpContainerLine->m_lpStg);
      }
   }

   if (hMetaPict)
      OleUIMetafilePictIconFree(hMetaPict); // clean up metafile
   return ResultFromScode(sc);
}

/* ContainerLine_GetOleObject
** --------------------------
**    return pointer to desired interface of embedded/linked object.
**
**    NOTE: this function causes an AddRef to the object. when the caller is
**          finished with the object, it must call Release.
**          this function does not AddRef the ContainerLine object.
*/
LPUNKNOWN ContainerLine_GetOleObject(
      LPCONTAINERLINE         lpContainerLine,
      REFIID                  riid
)
{
   /* if object is not already loaded, then load it now. objects are
   **    loaded lazily in this manner.
   */
   if (! lpContainerLine->m_lpOleObj)
      ContainerLine_LoadOleObject(lpContainerLine);

   if (lpContainerLine->m_lpOleObj)
      return OleStdQueryInterface(
            (LPUNKNOWN)lpContainerLine->m_lpOleObj,
            riid
      );
   else
      return NULL;
}



/* ContainerLine_RunOleObject
** --------------------------
**    Load and run the object. Upon running and if size of object has changed,
**    use SetExtent to change to new size.
**
*/
HRESULT ContainerLine_RunOleObject(LPCONTAINERLINE lpContainerLine)
{
   LPLINE lpLine = (LPLINE)lpContainerLine;
   SIZEL   sizelNew;
   HRESULT hrErr;
   HCURSOR  hPrevCursor;

   if (! lpContainerLine)
      return NOERROR;

   if (lpContainerLine->m_fGuardObj) {
      // object in process of creation--Fail to Run the object
      return ResultFromScode(E_FAIL);
   }

   if (lpContainerLine->m_lpOleObj &&
      OleIsRunning(lpContainerLine->m_lpOleObj))
      return NOERROR;     // object already running

   // this may take a while, put up hourglass cursor
   hPrevCursor = SetCursor(LoadCursor(NULL, IDC_WAIT));
   OLEDBG_BEGIN3("ContainerLine_RunOleObject\r\n")

   if (! lpContainerLine->m_lpOleObj) {
      if (! ContainerLine_LoadOleObject(lpContainerLine))
      {
         SetCursor(hPrevCursor);
         return ResultFromScode(E_OUTOFMEMORY); // Error: couldn't load obj
      }
   }

   OLEDBG_BEGIN2("OleRun called\r\n")
   hrErr = OleRun((LPUNKNOWN)lpContainerLine->m_lpOleObj);
   OLEDBG_END2

   if (hrErr != NOERROR) {
      SetCursor(hPrevCursor);     // restore original cursor

      OleDbgOutHResult("OleRun returned", hrErr);
      OLEDBG_END3
      return hrErr;
   }

   if (lpContainerLine->m_fDoSetExtent) {
      /* OLE2NOTE: the OLE object was resized when it was not running
      **    and the object did not have the OLEMISC_RECOMPOSEONRESIZE
      **    bit set. if it had, the object would have been run
      **    immediately when it was resized. this flag indicates that
      **    the object does something other than simple scaling when
      **    it is resized. because the object is being run now, we
      **    will call IOleObject::SetExtent.
      */
      lpContainerLine->m_fDoSetExtent = FALSE;

      // the size stored in our Line includes the border around the object.
      // we must subtract the border to get the size of the object itself.
      sizelNew.cx = lpLine->m_nWidthInHimetric;
      sizelNew.cy = lpLine->m_nHeightInHimetric;

      if ((sizelNew.cx != lpContainerLine->m_sizeInHimetric.cx) ||
         (sizelNew.cy != lpContainerLine->m_sizeInHimetric.cy)) {

         OLEDBG_BEGIN2("IOleObject::SetExtent called\r\n")
         lpContainerLine->m_lpOleObj->lpVtbl->SetExtent(
               lpContainerLine->m_lpOleObj,
               lpContainerLine->m_dwDrawAspect,
               (LPSIZEL)&sizelNew
         );
         OLEDBG_END2
      }
   }

   SetCursor(hPrevCursor);     // restore original cursor

   OLEDBG_END3
   return NOERROR;

}


/* ContainerLine_IsOleLink
** -----------------------
**
**    return TRUE if the ContainerLine has an OleLink.
**           FALSE if the ContainerLine has an embedding
*/
BOOL ContainerLine_IsOleLink(LPCONTAINERLINE lpContainerLine)
{
   if (!lpContainerLine)
      return FALSE;

   return (lpContainerLine->m_dwLinkType != 0);
}


/*  ContainerLine_Draw
**  ------------------
**
**      Draw a ContainerLine object on a DC.
**
**  Parameters:
**      hDC     - DC to which the line will be drawn
**      lpRect  - the object rect in logical coordinates
**      lpRectWBounds - bounding rect of the metafile underneath hDC
**                      (NULL if hDC is not a metafile DC)
**      fHighlight    - TRUE if line has selection highlight
*/
void ContainerLine_Draw(
      LPCONTAINERLINE         lpContainerLine,
      HDC                     hDC,
      LPRECT                  lpRect,
      LPRECT                  lpRectWBounds,
      BOOL                    fHighlight
)
{
   LPLINE  lpLine = (LPLINE) lpContainerLine;
   HRESULT hrErr = NOERROR;
   RECTL   rclHim;
   RECTL   rclHimWBounds;
   RECT    rcHim;

   if (lpContainerLine->m_fGuardObj) {
      // object in process of creation--do NOT try to draw
      return;
   }

   /* if object is not already loaded, then load it now. objects are
   **    loaded lazily in this manner.
   */
   if (! lpContainerLine->m_lpViewObj2) {
      if (! ContainerLine_LoadOleObject(lpContainerLine))
      {
         return;     // Error: could not load object
      }
   }

   if (lpRectWBounds) {
      rclHimWBounds.left      = (long) lpRectWBounds->left;
      rclHimWBounds.bottom    = (long) lpRectWBounds->bottom;
      rclHimWBounds.top       = (long) lpRectWBounds->top;
      rclHimWBounds.right     = (long) lpRectWBounds->right;
   }

   /* construct bounds rectangle for the object.
   **  offset origin for object to correct tab indentation
   */
   rclHim.left     = (long) lpRect->left;
   rclHim.bottom   = (long) lpRect->bottom;
   rclHim.top      = (long) lpRect->top;
   rclHim.right    = (long) lpRect->right;

   rclHim.left += (long) ((LPLINE)lpContainerLine)->m_nTabWidthInHimetric;
   rclHim.right += (long) ((LPLINE)lpContainerLine)->m_nTabWidthInHimetric;

#if defined( INPLACE_CNTR )
   /* OLE2NOTE: if the OLE object currently has a visible in-place
   **    window, then we do NOT want to draw on top of its window.
   **    this could interfere with the object's display.
   */
   if ( !lpContainerLine->m_fIpVisible )
#endif
   {
   hrErr = lpContainerLine->m_lpViewObj2->lpVtbl->Draw(
         lpContainerLine->m_lpViewObj2,
         lpContainerLine->m_dwDrawAspect,
         -1,
         NULL,
         NULL,
         NULL,
         hDC,
         (LPRECTL)&rclHim,
         (lpRectWBounds ? (LPRECTL)&rclHimWBounds : NULL),
         NULL,
         0
   );
   if (hrErr != NOERROR)
      OleDbgOutHResult("IViewObject::Draw returned", hrErr);

   if (lpContainerLine->m_fObjWinOpen)
      {
      rcHim.left      = (int) rclHim.left;
      rcHim.top       = (int) rclHim.top;
      rcHim.right     = (int) rclHim.right;
      rcHim.bottom    = (int) rclHim.bottom;

      /* OLE2NOTE: if the object servers window is Open (ie. not active
      **    in-place) then we must shade the object in our document to
      **    indicate to the user that the object is open elsewhere.
      */
      OleUIDrawShading((LPRECT)&rcHim, hDC, OLEUI_SHADE_FULLRECT, 0);
      }
   }

   /* if the object associated with the ContainerLine is an automatic
   **    link then try to connect it with its LinkSource if the
   **    LinkSource is already running. we do not want to force the
   **    LinkSource to run.
   **
   **    OLE2NOTE: a sophistocated container will want to continually
   **    attempt to connect its automatic links. OLE does NOT
   **    automatically connect links when link sources become
   **    available. some containers will want to attempt to connect
   **    its links as part of idle time processing. another strategy
   **    is to attempt to connect an automatic link every time it is
   **    drawn on the screen. (this is the strategy used by this
   **    CntrOutl sample application.)
   */
   if (lpContainerLine->m_dwLinkType == OLEUPDATE_ALWAYS)
      ContainerLine_BindLinkIfLinkSrcIsRunning(lpContainerLine);

   return;
}


void ContainerLine_DrawSelHilight(
      LPCONTAINERLINE lpContainerLine,
      HDC             hDC,            // MM_TEXT mode
      LPRECT          lprcPix,        // listbox rect
      UINT            itemAction,
      UINT            itemState
)
{
   LPLINE  lpLine = (LPLINE)lpContainerLine;
   RECT    rcObj;
   DWORD   dwFlags = OLEUI_HANDLES_INSIDE | OLEUI_HANDLES_USEINVERSE;
   int     nHandleSize;
   LPCONTAINERDOC lpContainerDoc;

   if (!lpContainerLine || !hDC || !lprcPix)
      return;

   lpContainerDoc = lpContainerLine->m_lpDoc;

   // Get size of OLE object
   ContainerLine_GetOleObjectRectInPixels(lpContainerLine, (LPRECT)&rcObj);

   nHandleSize = GetProfileInt("windows", "oleinplaceborderwidth",
         DEFAULT_HATCHBORDER_WIDTH) + 1;

   OleUIDrawHandles((LPRECT)&rcObj, hDC, dwFlags, nHandleSize, TRUE);
}

/* InvertDiffRect
** --------------
**
**    Paint the surrounding of the Obj rect black but within lprcPix
**      (similar to the lprcPix minus lprcObj)
*/
static void InvertDiffRect(LPRECT lprcPix, LPRECT lprcObj, HDC hDC)
{
   RECT rcBlack;

   // draw black in all space outside of object's rectangle
   rcBlack.top = lprcPix->top;
   rcBlack.bottom = lprcPix->bottom;

   rcBlack.left = lprcPix->left + 1;
   rcBlack.right = lprcObj->left - 1;
   InvertRect(hDC, (LPRECT)&rcBlack);

   rcBlack.left = lprcObj->right + 1;
   rcBlack.right = lprcPix->right - 1;
   InvertRect(hDC, (LPRECT)&rcBlack);

   rcBlack.top = lprcPix->top;
   rcBlack.bottom = lprcPix->top + 1;
   rcBlack.left = lprcObj->left - 1;
   rcBlack.right = lprcObj->right + 1;
   InvertRect(hDC, (LPRECT)&rcBlack);

   rcBlack.top = lprcPix->bottom;
   rcBlack.bottom = lprcPix->bottom - 1;
   rcBlack.left = lprcObj->left - 1;
   rcBlack.right = lprcObj->right + 1;
   InvertRect(hDC, (LPRECT)&rcBlack);
}


/* Edit the ContainerLine line object.
**      returns TRUE if line was changed
**              FALSE if the line was NOT changed
*/
BOOL ContainerLine_Edit(LPCONTAINERLINE lpContainerLine, HWND hWndDoc,HDC hDC)
{
   ContainerLine_DoVerb(lpContainerLine, OLEIVERB_PRIMARY, NULL, TRUE, TRUE);

   /* assume object was NOT changed, if it was obj will send Changed
   **    or Saved notification.
   */
   return FALSE;
}



/* ContainerLine_SetHeightInHimetric
** ---------------------------------
**
** Set the height of a ContainerLine object. The widht will be changed
** to keep the aspect ratio
*/
void ContainerLine_SetHeightInHimetric(LPCONTAINERLINE lpContainerLine, int nHeight)
{
   LPLINE  lpLine = (LPLINE)lpContainerLine;
   SIZEL   sizelOleObject;
   HRESULT hrErr;

   if (!lpContainerLine)
      return;

   if (lpContainerLine->m_fGuardObj) {
      // object in process of creation--Fail to set the Height
      return;
   }

   if (nHeight != -1) {
      BOOL    fMustClose = FALSE;
      BOOL    fMustRun   = FALSE;

      /* if object is not already loaded, then load it now. objects are
      **    loaded lazily in this manner.
      */
      if (! lpContainerLine->m_lpOleObj)
         ContainerLine_LoadOleObject(lpContainerLine);

      // the height argument specifies the desired height for the Line.
      sizelOleObject.cy = nHeight;

      // we will calculate the corresponding width for the object by
      // maintaining the current aspect ratio of the object.
      sizelOleObject.cx = (int)(sizelOleObject.cy *
            lpContainerLine->m_sizeInHimetric.cx /
            lpContainerLine->m_sizeInHimetric.cy);

      /* OLE2NOTE: if the OLE object is already running then we can
      **    immediately call SetExtent. But, if the object is NOT
      **    currently running then we will check if the object
      **    indicates that it is normally recomposes itself on
      **    resizing. ie. that the object does not simply scale its
      **    display when it it resized. if so then we will force the
      **    object to run so that we can call IOleObject::SetExtent.
      **    SetExtent does not have any effect if the object is only
      **    loaded. if the object does NOT indicate that it
      **    recomposes on resize (OLEMISC_RECOMPOSEONRESIZE) then we
      **    will wait till the next time that the object is run to
      **    call SetExtent. we will store a flag in the ContainerLine
      **    to indicate that a SetExtent is necessary. It is
      **    necessary to persist this flag.
      */
      if (! OleIsRunning(lpContainerLine->m_lpOleObj)) {
         DWORD dwStatus;

         OLEDBG_BEGIN2("IOleObject::GetMiscStatus called\r\n")
         hrErr = lpContainerLine->m_lpOleObj->lpVtbl->GetMiscStatus(
               lpContainerLine->m_lpOleObj,
               lpContainerLine->m_dwDrawAspect,
               (LPDWORD)&dwStatus
         );
         OLEDBG_END2
         if (hrErr == NOERROR && (dwStatus & OLEMISC_RECOMPOSEONRESIZE)) {
            // force the object to run
            ContainerLine_RunOleObject(lpContainerLine);
            fMustClose = TRUE;
         } else {
            /*  the OLE object is NOT running and does NOT
            **    recompose on resize. simply scale the object now
            **    and do the SetExtent the next time the object is
            **    run. we set the Line to the new size even though
            **    the object's extents have not been changed.
            **    this has the result of scaling the object's
            **    display to the new size.
            */
            lpContainerLine->m_fDoSetExtent = TRUE;
            ContainerLine_SetLineHeightFromObjectExtent(
                  lpContainerLine, (LPSIZEL)&sizelOleObject);
            return;
         }
      }

      OLEDBG_BEGIN2("IOleObject::SetExtent called\r\n")
      hrErr = lpContainerLine->m_lpOleObj->lpVtbl->SetExtent(
            lpContainerLine->m_lpOleObj,
            lpContainerLine->m_dwDrawAspect,
            (LPSIZEL)&sizelOleObject);
      OLEDBG_END2

      if (hrErr != NOERROR) {
         /* OLE Object refuses to take on the new extents. Set the
         **    Line to the new size even though the object refused
         **    the new extents. this has the result of scaling the
         **    object's display to the new size.
         **
         **    if the object HAD accepted the new extents, then it
         **    will send out an OnViewChange/OnDataChange
         **    notification. this results in our container receiving
         **    an OnViewChange notification; the line height will be
         **    reset when this notification is received.
         */
         ContainerLine_SetLineHeightFromObjectExtent(
               lpContainerLine, (LPSIZEL)&sizelOleObject);
      }

      if (fMustClose)
         ContainerLine_CloseOleObject(
               lpContainerLine, OLECLOSE_SAVEIFDIRTY);
   }
   else {
      /* Set the line to default height given the natural (unscaled)
      **    extents of the OLE object.
      */
      ContainerLine_SetLineHeightFromObjectExtent(
            lpContainerLine,(LPSIZEL)&lpContainerLine->m_sizeInHimetric);
   }
}


/*  ContainerLine_SetLineHeightFromObjectExtent
 *
 *  Purpose:
 *      Calculate the corresponding line height from the OleObject size
 *      Scale the line height to fit the limit if necessary
 *
 *  Parameters:
 *      lpsizelOleObject        pointer to size of OLE Object
 *
 *  Returns:
 *      nil
 */
void ContainerLine_SetLineHeightFromObjectExtent(
      LPCONTAINERLINE         lpContainerLine,
      LPSIZEL                 lpsizelOleObject
)
{
   LPLINE lpLine = (LPLINE)lpContainerLine;

   UINT uMaxObjectHeight = XformHeightInPixelsToHimetric(NULL,
         LISTBOX_HEIGHT_LIMIT);

   if (!lpContainerLine || !lpsizelOleObject)
      return;

   if (lpContainerLine->m_fGuardObj) {
      // object in process of creation--Fail to set the Height
      return;
   }

   lpLine->m_nWidthInHimetric = (int)lpsizelOleObject->cx;
   lpLine->m_nHeightInHimetric = (int)lpsizelOleObject->cy;

   // Rescale the object if height is greater than the limit
   if (lpLine->m_nHeightInHimetric > (UINT)uMaxObjectHeight) {

      lpLine->m_nWidthInHimetric = (UINT)
            ((long)lpLine->m_nWidthInHimetric *
            (long)uMaxObjectHeight /
            (long)lpLine->m_nHeightInHimetric);

      lpLine->m_nHeightInHimetric = uMaxObjectHeight;
   }

}


/* ContainerLine_SaveToStg
** -----------------------
**    Save a given ContainerLine and associated OLE object to an IStorage*.
*/
BOOL ContainerLine_SaveToStm(
      LPCONTAINERLINE         lpContainerLine,
      LPSTREAM                lpLLStm
)
{
   CONTAINERLINERECORD_ONDISK objLineRecord;
   ULONG nWritten;
   HRESULT hrErr;
   //  Compilers should handle alignment correctly

   OLESTRCPY(objLineRecord.m_szStgName, lpContainerLine->m_szStgName);
   objLineRecord.m_fMonikerAssigned = (USHORT)lpContainerLine->m_fMonikerAssigned;
   objLineRecord.m_dwDrawAspect = lpContainerLine->m_dwDrawAspect;
   objLineRecord.m_sizeInHimetric = lpContainerLine->m_sizeInHimetric;
   objLineRecord.m_dwLinkType = lpContainerLine->m_dwLinkType;
   objLineRecord.m_fDoSetExtent = (USHORT) lpContainerLine->m_fDoSetExtent;

   /* write line record */
   hrErr = lpLLStm->lpVtbl->Write(
         lpLLStm,
         (LPVOID)&objLineRecord,
         sizeof(objLineRecord),
         &nWritten
   );

   if (hrErr != NOERROR) {
      OleDbgAssertSz(hrErr == NOERROR,"Could not write to LineList stream");
    {
      return FALSE;
    }
   }

   return TRUE;
}


/* ContainerLine_SaveOleObjectToStg
** --------------------------------
**    Save the OLE object associated with the ContainerLine to an IStorage*.
*/
BOOL ContainerLine_SaveOleObjectToStg(
      LPCONTAINERLINE         lpContainerLine,
      LPSTORAGE               lpSrcStg,
      LPSTORAGE               lpDestStg,
      BOOL                    fRemember
)
{
   HRESULT         hrErr;
   SCODE           sc = S_OK;
   BOOL            fStatus;
   BOOL            fSameAsLoad = (lpSrcStg==lpDestStg ? TRUE : FALSE);
   LPSTORAGE       lpObjDestStg;

   if (lpContainerLine->m_fGuardObj) {
      // object in process of creation--Fail to save
      return FALSE;
   }

   if (! lpContainerLine->m_lpOleObj) {

      /*****************************************************************
      ** CASE 1: object is NOT loaded.
      *****************************************************************/

      if (fSameAsLoad) {
         /*************************************************************
         ** CASE 1A: we are saving to the current storage. because
         **    the object is not loaded, it is up-to-date
         **    (ie. nothing to do).
         *************************************************************/

         ;

      } else {
         /*************************************************************
         ** CASE 1B: we are saving to a new storage. because
         **    the object is not loaded, we can simply copy the
         **    object's current storage to the new storage.
         *************************************************************/

         /* if current object storage is not already open, then open it */
         if (! lpContainerLine->m_lpStg) {
            lpContainerLine->m_lpStg = OleStdOpenChildStorage(
                  lpSrcStg,
                  lpContainerLine->m_szStgName,
                  STGM_READWRITE
               );
            if (lpContainerLine->m_lpStg == NULL) {
#if defined( _DEBUG )
               OleDbgAssertSz(
                     lpContainerLine->m_lpStg != NULL,
                     "Error opening child stg"
               );
#endif
               return FALSE;
            }
         }

         /* Create a child storage inside the destination storage. */
         lpObjDestStg = OleStdCreateChildStorage(
               lpDestStg,
               lpContainerLine->m_szStgName
         );

         if (lpObjDestStg == NULL) {
#if defined( _DEBUG )
            OleDbgAssertSz(
                  lpObjDestStg != NULL,
                  "Could not create obj storage!"
            );
#endif
            return FALSE;
         }

         hrErr = lpContainerLine->m_lpStg->lpVtbl->CopyTo(
               lpContainerLine->m_lpStg,
               0,
               NULL,
               NULL,
               lpObjDestStg
         );
         // REVIEW: should we handle error here?
         fStatus = OleStdCommitStorage(lpObjDestStg);

         /* if we are supposed to remember this storage as the new
         **    storage for the object, then release the old one and
         **    save the new one. else, throw away the new one.
         */
         if (fRemember) {
            OleStdVerifyRelease(
                  (LPUNKNOWN)lpContainerLine->m_lpStg,
                  OLESTR("Original object stg not released")
            );
            lpContainerLine->m_lpStg = lpObjDestStg;
         } else {
            OleStdVerifyRelease(
                  (LPUNKNOWN)lpObjDestStg,
                  OLESTR("Copied object stg not released")
            );
         }
      }

   } else {

      /*****************************************************************
      ** CASE 2: object IS loaded.
      *****************************************************************/

      if (fSameAsLoad) {
         /*************************************************************
         ** CASE 2A: we are saving to the current storage. if the object
         **    is not dirty, then the current storage is up-to-date
         **    (ie. nothing to do).
         *************************************************************/

         LPPERSISTSTORAGE lpPersistStg = lpContainerLine->m_lpPersistStg;
         OleDbgAssert(lpPersistStg);

         hrErr = lpPersistStg->lpVtbl->IsDirty(lpPersistStg);

         /* OLE2NOTE: we will only accept an explicit "no i
         **    am NOT dirty statement" (ie. S_FALSE) as an
         **    indication that the object is clean. eg. if
         **    the object returns E_NOTIMPL we must
         **    interpret it as the object IS dirty.
         */
         if (GetScode(hrErr) != S_FALSE) {

            /* OLE object IS dirty */

            OLEDBG_BEGIN2("OleSave called\r\n")
            hrErr = OleSave(
                  lpPersistStg, lpContainerLine->m_lpStg, fSameAsLoad);
            OLEDBG_END2

            if (hrErr != NOERROR) {
               OleDbgOutHResult("WARNING: OleSave returned", hrErr);
               sc = GetScode(hrErr);
            }

            // OLE2NOTE: if OleSave fails, SaveCompleted must be called.
            OLEDBG_BEGIN2("IPersistStorage::SaveCompleted called\r\n")
            hrErr=lpPersistStg->lpVtbl->SaveCompleted(lpPersistStg,NULL);
            OLEDBG_END2

            if (hrErr != NOERROR) {
               OleDbgOutHResult("WARNING: SaveCompleted returned",hrErr);
               if (sc == S_OK)
                  sc = GetScode(hrErr);
            }

            if (sc != S_OK)
            {
               return FALSE;
            }
         }

      } else {
         /*************************************************************
         ** CASE 2B: we are saving to a new storage. we must
         **    tell the object to save into the new storage.
         *************************************************************/

         LPPERSISTSTORAGE lpPersistStg = lpContainerLine->m_lpPersistStg;

         if (! lpPersistStg) return FALSE;

         /* Create a child storage inside the destination storage. */
         lpObjDestStg = OleStdCreateChildStorage(
               lpDestStg,
               lpContainerLine->m_szStgName
         );

         if (lpObjDestStg == NULL) {
#if defined( _DEBUG )
            OleDbgAssertSz(
                  lpObjDestStg != NULL,
                  "Could not create object storage!"
            );
#endif
            return FALSE;
         }

         OLEDBG_BEGIN2("OleSave called\r\n")
         hrErr = OleSave(lpPersistStg, lpObjDestStg, fSameAsLoad);
         OLEDBG_END2

         // OLE2NOTE: even if OleSave fails, must still call SaveCompleted
         if (hrErr != NOERROR) {
            OleDbgOutHResult("WARNING: OleSave returned", hrErr);
            sc = GetScode(hrErr);
         }

         /* OLE2NOTE: a root level container should immediately
         **    call IPersistStorage::SaveCompleted after calling
         **    OleSave. a nested level container should not call
         **    SaveCompleted now, but must wait until SaveCompleted
         **    is call on it by its container. since our container
         **    is not a container/server, then we always call
         **    SaveComplete here.
         **
         **    if this is a SaveAs operation, then we need to pass
         **    the lpStg back in SaveCompleted to inform the object
         **    of its new storage that it may hold on to. if this is
         **    a Save or a SaveCopyAs operation, then we simply pass
         **    NULL in SaveCompleted; the object can continue to hold
         **    its current storage. if an error occurs during the
         **    OleSave call we must still call SaveCompleted but we
         **    must pass NULL.
         */
         OLEDBG_BEGIN2("IPersistStorage::SaveCompleted called\r\n")
         hrErr = lpPersistStg->lpVtbl->SaveCompleted(
               lpPersistStg,
               ((FAILED(sc) || !fRemember) ? NULL : lpObjDestStg)
         );
         OLEDBG_END2

         if (hrErr != NOERROR) {
            OleDbgOutHResult("WARNING: SaveCompleted returned",hrErr);
            if (sc == S_OK)
               sc = GetScode(hrErr);
         }

         if (sc != S_OK) {
            OleStdVerifyRelease(
                  (LPUNKNOWN)lpObjDestStg,
                  OLESTR("Copied object stg not released")
            );
            return FALSE;
         }

         /* if we are supposed to remember this storage as the new
         **    storage for the object, then release the old one and
         **    save the new one. else, throw away the new one.
         */
         if (fRemember) {
            OleStdVerifyRelease(
                  (LPUNKNOWN)lpContainerLine->m_lpStg,
                  OLESTR("Original object stg not released")
            );
            lpContainerLine->m_lpStg = lpObjDestStg;
         } else {
            OleStdVerifyRelease(
                  (LPUNKNOWN)lpObjDestStg,
                  OLESTR("Copied object stg not released")
            );
         }
      }
   }

   /* OLE2NOTE: after saving an OLE object it is possible that it sent
   **    an OnViewChange notification because it had been modified. in
   **    this situation it is possible that the extents of the object
   **    have changed. if so we want to relayout the space for the
   **    object immediately so that the extent information saved with
   **    the ContainerLine match the data saved with the OLE object
   **    itself.
   */
   if (lpContainerLine->m_fDoGetExtent) {
      BOOL fSizeChanged = ContainerLine_UpdateExtent(lpContainerLine, NULL);
#if defined( INPLACE_CNTR )
      /* if the extents of this ContainerLine have changed, then we
      **    need to reset the fDoGetExtent flag to TRUE so that later
      **    when ContainerDoc_UpdateExtentOfAllOleObjects is called
      **    (when the WM_U_UPDATEOBJECTEXTENT message is processed),
      **    it is recognized that the extents of this line have
      **    changed. if any line changes size, then any in-place
      **    active object below this line must be told to update the
      **    position of their windows (via SetObjectRects -- see
      **    ContainerDoc_UpdateInPlaceObjectRects function).
      */
      lpContainerLine->m_fDoGetExtent = fSizeChanged;
#endif
   }

   return TRUE;
}


/* ContainerLine_LoadFromStg
** -------------------------
**    Create a ContainerLine object and initialize it with data that
**    was previously writen to an IStorage*. this function does not
**    immediately OleLoad the associated OLE object, only the data of
**    the ContainerLine object itself is loaded from the IStorage*.
*/
LPLINE ContainerLine_LoadFromStg(
      LPSTORAGE               lpSrcStg,
      LPSTREAM                lpLLStm,
      LPOUTLINEDOC            lpDestDoc
)
{
   HDC         hDC;
   LPLINELIST  lpDestLL = &lpDestDoc->m_LineList;
   ULONG nRead;
   HRESULT hrErr;
   LPCONTAINERLINE lpContainerLine;
   CONTAINERLINERECORD_ONDISK objLineRecord;

   lpContainerLine=(LPCONTAINERLINE) New((DWORD)sizeof(objLineRecord));
   if (lpContainerLine == NULL) {
      OleDbgAssertSz(lpContainerLine!=NULL,"Error allocating ContainerLine");
      return NULL;
   }

   hDC = LineList_GetDC(lpDestLL);
   ContainerLine_Init(lpContainerLine, 0, hDC);
   LineList_ReleaseDC(lpDestLL, hDC);

   /* OLE2NOTE: In order to have a stable ContainerLine object we must
   **    AddRef the object's refcnt. this will be later released when
   **    the ContainerLine is deleted.
   */
   ContainerLine_AddRef(lpContainerLine);

   lpContainerLine->m_lpDoc = (LPCONTAINERDOC) lpDestDoc;

   /* read line record */
   hrErr = lpLLStm->lpVtbl->Read(
         lpLLStm,
         (LPVOID)&objLineRecord,
         sizeof(CONTAINERLINERECORD),
         &nRead
   );

   if (hrErr != NOERROR) {
      OleDbgAssertSz(hrErr==NOERROR, "Could not read from LineList stream");
      goto error;
   }

   //  Compilers should handle alignment correctly
   OLESTRCPY(lpContainerLine->m_szStgName, objLineRecord.m_szStgName);
   lpContainerLine->m_fMonikerAssigned = (BOOL) objLineRecord.m_fMonikerAssigned;
   lpContainerLine->m_dwDrawAspect = objLineRecord.m_dwDrawAspect;
   lpContainerLine->m_sizeInHimetric = objLineRecord.m_sizeInHimetric;
   lpContainerLine->m_dwLinkType = objLineRecord.m_dwLinkType;
   lpContainerLine->m_fDoSetExtent = (BOOL) objLineRecord.m_fDoSetExtent;

   return (LPLINE)lpContainerLine;

error:
   // destroy partially created ContainerLine
   if (lpContainerLine)
      ContainerLine_Delete(lpContainerLine);
   return NULL;
}


/* ContainerLine_GetTextLen
 * ------------------------
 *
 * Return length of the string representation of the ContainerLine
 *  (not considering the tab level). we will use the following as the
 *  string representation of a ContainerLine:
 *      "<" + user type name of OLE object + ">"
 *  eg:
 *      <Microsoft Excel Worksheet>
 */
int ContainerLine_GetTextLen(LPCONTAINERLINE lpContainerLine)
{
   LPOLESTR   lpszUserType = NULL;
   HRESULT hrErr;
   int     nLen;
   BOOL    fIsLink = ContainerLine_IsOleLink(lpContainerLine);

   /* if object is not already loaded, then load it now. objects are
   **    loaded lazily in this manner.
   */
   if (! lpContainerLine->m_lpOleObj)
      ContainerLine_LoadOleObject(lpContainerLine);

   OLEDBG_BEGIN2("IOleObject::GetUserType called\r\n")
   hrErr = lpContainerLine->m_lpOleObj->lpVtbl->GetUserType(
         lpContainerLine->m_lpOleObj,
         USERCLASSTYPE_FULL,
         &lpszUserType
   );
   OLEDBG_END2

   if (hrErr != NOERROR)   {
      // user type is NOT available
      nLen = sizeof(UNKNOWN_OLEOBJ_TYPE) + 2; // allow space for '<' + '>'
      nLen += lstrlen((LPSTR)(fIsLink ? szOLELINK : szOLEOBJECT)) + 1;
   } else {
      nLen = OLESTRLEN(lpszUserType) + 2;   // allow space for '<' + '>'
      nLen += OLESTRLEN((LPOLESTR)(fIsLink ? szOLELINK : szOLEOBJECT)) + 1;

      /* OLE2NOTE: we must free the string that was allocated by the
      **    IOleObject::GetUserType method.
      */
      OleStdFreeString(lpszUserType, NULL);
   }

   return nLen;
}


/* ContainerLine_GetTextData
 * -------------------------
 *
 * Return the string representation of the ContainerLine
 *  (not considering the tab level). we will use the following as the
 *  string representation of a ContainerLine:
 *      "<" + user type name of OLE object + ">"
 *  eg:
 *      <Microsoft Excel Worksheet>
 */
void ContainerLine_GetTextData(LPCONTAINERLINE lpContainerLine, LPOLESTR lpszBuf)
{
   LPOLESTR   lpszUserType = NULL;
   BOOL    fIsLink = ContainerLine_IsOleLink(lpContainerLine);
   HRESULT hrErr;
   char    szAnsiBuf[256], szAnsiUserType[256], szAnsiString[256];
   LPOLESTR lpszUniStr;

   /* if object is not already loaded, then load it now. objects are
   **    loaded lazily in this manner.
   */
   if (! lpContainerLine->m_lpOleObj)
      ContainerLine_LoadOleObject(lpContainerLine);

   hrErr = lpContainerLine->m_lpOleObj->lpVtbl->GetUserType(
         lpContainerLine->m_lpOleObj,
         USERCLASSTYPE_FULL,
         &lpszUserType
   );

   W2A (lpszBuf, szAnsiBuf, 256);
   W2A (lpszUserType, szAnsiUserType, 256);
   lpszUniStr = (fIsLink ? szOLELINK : szOLEOBJECT);
   W2A (lpszUniStr, szAnsiString, 256);
   if (hrErr != NOERROR)   {
      // user type is NOT available
      wsprintf(
            szAnsiBuf,
            "<%s %s>",
            UNKNOWN_OLEOBJ_TYPE,
            (LPSTR)szAnsiString
      );
   } else {
      wsprintf(
            szAnsiBuf,
            "<%s %s>",
            szAnsiUserType,
            (LPSTR)szAnsiString
      );

      /* OLE2NOTE: we must free the string that was allocated by the
      **    IOleObject::GetUserType method.
      */
      OleStdFreeString(lpszUserType, NULL);
   }
}


/* ContainerLine_GetOutlineData
 * ----------------------------
 *
 * Return the CF_OUTLINE format data for the ContainerLine.
 */
BOOL ContainerLine_GetOutlineData(
      LPCONTAINERLINE         lpContainerLine,
      LPTEXTLINE              lpBuf
)
{
   LPLINE      lpLine = (LPLINE)lpContainerLine;
   LPLINELIST  lpLL = &((LPOUTLINEDOC)lpContainerLine->m_lpDoc)->m_LineList;
   HDC         hDC;
   OLECHAR     szTmpBuf[MAXSTRLEN+1];
   char        szAnsiStr[256];
   LPTEXTLINE  lpTmpTextLine;

   // Create a TextLine with the Text representation of the ContainerLine.
   ContainerLine_GetTextData(lpContainerLine, (LPOLESTR)szTmpBuf);

   hDC = LineList_GetDC(lpLL);
   W2A (szTmpBuf, szAnsiStr, 256);
   lpTmpTextLine = TextLine_Create(hDC, lpLine->m_nTabLevel, szAnsiStr);
   LineList_ReleaseDC(lpLL, hDC);

   TextLine_Copy(lpTmpTextLine, lpBuf);

   // Delete the temporary TextLine
   TextLine_Delete(lpTmpTextLine);
   return TRUE;
}


/* ContainerLine_GetPosRect
** -----------------------
**    Get the PosRect in client coordinates for the OLE object's window.
**
** OLE2NOTE: the PosRect must take into account the scroll postion of
**    the document window.
*/
void ContainerLine_GetPosRect(
      LPCONTAINERLINE     lpContainerLine,
      LPRECT              lprcPosRect
)
{
   ContainerLine_GetOleObjectRectInPixels(lpContainerLine,lprcPosRect);

   // shift rect for left margin
   lprcPosRect->left += lpContainerLine->m_nHorizScrollShift;
   lprcPosRect->right += lpContainerLine->m_nHorizScrollShift;
}


/* ContainerLine_GetOleObjectRectInPixels
** --------------------------------------
**    Get the extent of the OLE Object contained in the given Line in
**    client coordinates after scaling.
*/
void ContainerLine_GetOleObjectRectInPixels(LPCONTAINERLINE lpContainerLine, LPRECT lprc)
{
   LPOUTLINEDOC lpOutlineDoc;
   LPSCALEFACTOR lpscale;
   LPLINELIST lpLL;
   LPLINE lpLine;
   int nIndex;
   HDC hdcLL;

   if (!lpContainerLine || !lprc)
      return;

   lpOutlineDoc = (LPOUTLINEDOC)lpContainerLine->m_lpDoc;
   lpscale = OutlineDoc_GetScaleFactor(lpOutlineDoc);
   lpLL = OutlineDoc_GetLineList(lpOutlineDoc);
   lpLine = (LPLINE)lpContainerLine;
   nIndex = LineList_GetLineIndex(lpLL, lpLine);

   LineList_GetLineRect(lpLL, nIndex, lprc);

   hdcLL = GetDC(lpLL->m_hWndListBox);

   /* lprc is set to be size of Line Object (including the boundary) */
   lprc->left += (int)(
         (long)XformWidthInHimetricToPixels(hdcLL,
               lpLine->m_nTabWidthInHimetric +
               LOWORD(OutlineDoc_GetMargin(lpOutlineDoc))) *
         lpscale->dwSxN / lpscale->dwSxD);
   lprc->right = (int)(
         lprc->left + (long)
         XformWidthInHimetricToPixels(hdcLL, lpLine->m_nWidthInHimetric) *
         lpscale->dwSxN / lpscale->dwSxD);

   ReleaseDC(lpLL->m_hWndListBox, hdcLL);
}


/* ContainerLine_GetOleObjectSizeInHimetric
** ----------------------------------------
**    Get the size of the OLE Object contained in the given Line
*/
void ContainerLine_GetOleObjectSizeInHimetric(LPCONTAINERLINE lpContainerLine, LPSIZEL lpsizel)
{
   if (!lpContainerLine || !lpsizel)
      return;

   *lpsizel = lpContainerLine->m_sizeInHimetric;
}


/* ContainerLine_BindLinkIfLinkSrcIsRunning
** ----------------------------------------
**    Try to connect the OLE link object associated with the
**    ContainerLine with its LinkSource if the LinkSource is already
**    running and the link is an automatic link. we do not want to
**    force the LinkSource to run.
**
**    OLE2NOTE: a sophistocated container will want to continually
**    attempt to connect its automatic links. OLE does NOT
**    automatically connect links when link source become available. some
**    containers will want to attempt to connect its links as part of
**    idle time processing. another strategy is to attempt to connect
**    an automatic link every time it is drawn on the screen. (this is
**    the strategy used by this CntrOutl sample application.)
*/
void ContainerLine_BindLinkIfLinkSrcIsRunning(LPCONTAINERLINE lpContainerLine)
{
   LPOLEAPP lpOleApp = (LPOLEAPP)g_lpApp;
   HRESULT hrErr;
   BOOL fPrevEnable1;
   BOOL fPrevEnable2;

   // if the link source is known to be un-bindable, then don't even try
   if (lpContainerLine->m_fLinkUnavailable)
      return;

   /* OLE2NOTE: we do not want to ever give the Busy/NotResponding
   **    dialogs when we are attempting to BindIfRunning to the link
   **    source. if the link source is currently busy, this could
   **    cause the Busy dialog to come up. even if the link source is
   **    busy, we do not want put up the busy dialog. thus we will
   **    disable the dialog and later re-enable them
   */
   OleApp_DisableBusyDialogs(lpOleApp, &fPrevEnable1, &fPrevEnable2);

   OLEDBG_BEGIN2("IOleLink::BindIfRunning called\r\n")
   hrErr = lpContainerLine->m_lpOleLink->lpVtbl->BindIfRunning(
         lpContainerLine->m_lpOleLink);
   OLEDBG_END2

   // re-enable the Busy/NotResponding dialogs
   OleApp_EnableBusyDialogs(lpOleApp, fPrevEnable1, fPrevEnable2);
}
