/*************************************************************************
**
**    OLE 2 Sample Code
**
**    linking.c
**
**    This file contains the major interfaces, methods and related support
**    functions for implementing linking to items. The code
**    contained in this file is used by BOTH the Container and Server
**    (Object) versions of the Outline sample code.
**
**    As a server SVROUTL supports linking to the whole document object
**    (either a file-based document or as an embedded object). It also
**    supports linking to ranges (or PseudoObjects).
**
**    As a container CNTROUTL supports linking to embedded objects.
**    (see file svrpsobj.c for Pseudo Object implementation)
**
**    OleDoc Object
**      exposed interfaces:
**          IPersistFile
**          IOleItemContainer
**          IExternalConnection
**
**    (c) Copyright Microsoft Corp. 1992 - 1993 All Rights Reserved
**
*************************************************************************/

#include "outline.h"

OLEDBGDATA

extern LPOUTLINEAPP             g_lpApp;


/*************************************************************************
** OleDoc::IPersistFile interface implementation
*************************************************************************/

// IPersistFile::QueryInterface
STDMETHODIMP OleDoc_PFile_QueryInterface(
      LPPERSISTFILE       lpThis,
      REFIID              riid,
      LPVOID FAR*         lplpvObj
)
{
   LPOLEDOC lpOleDoc = ((struct CDocPersistFileImpl FAR*)lpThis)->lpOleDoc;

   return OleDoc_QueryInterface(lpOleDoc, riid, lplpvObj);
}


// IPersistFile::AddRef
STDMETHODIMP_(ULONG) OleDoc_PFile_AddRef(LPPERSISTFILE lpThis)
{
   LPOLEDOC lpOleDoc = ((struct CDocPersistFileImpl FAR*)lpThis)->lpOleDoc;

   OleDbgAddRefMethod(lpThis, "IPersistFile");

   return OleDoc_AddRef(lpOleDoc);
}


// IPersistFile::Release
STDMETHODIMP_(ULONG) OleDoc_PFile_Release (LPPERSISTFILE lpThis)
{
   LPOLEDOC lpOleDoc = ((struct CDocPersistFileImpl FAR*)lpThis)->lpOleDoc;

   OleDbgReleaseMethod(lpThis, "IPersistFile");

   return OleDoc_Release(lpOleDoc);
}


// IPersistFile::GetClassID
STDMETHODIMP OleDoc_PFile_GetClassID (
      LPPERSISTFILE       lpThis,
      CLSID FAR*          lpclsid
)
{
   LPOLEDOC lpOleDoc = ((struct CDocPersistFileImpl FAR*)lpThis)->lpOleDoc;
   LPOLEAPP lpOleApp = (LPOLEAPP)g_lpApp;
   OleDbgOut2("OleDoc_PFile_GetClassID\r\n");

#if defined( OLE_SERVER ) && defined( SVR_TREATAS )

   /* OLE2NOTE: we must be carefull to return the correct CLSID here.
   **    if we are currently preforming a "TreatAs (aka. ActivateAs)"
   **    operation then we need to return the class of the object
   **    written in the storage of the object. otherwise we would
   **    return our own class id.
   */
   return ServerDoc_GetClassID((LPSERVERDOC)lpOleDoc, lpclsid);
#else
   *lpclsid = CLSID_APP;
#endif
   return NOERROR;
}


// IPersistFile::IsDirty
STDMETHODIMP  OleDoc_PFile_IsDirty(LPPERSISTFILE lpThis)
{
   LPOLEDOC lpOleDoc = ((struct CDocPersistFileImpl FAR*)lpThis)->lpOleDoc;
   OleDbgOut2("OleDoc_PFile_IsDirty\r\n");

   if (OutlineDoc_IsModified((LPOUTLINEDOC)lpOleDoc))
      return NOERROR;
   else
      return ResultFromScode(S_FALSE);
}


// IPersistFile::Load
STDMETHODIMP OleDoc_PFile_Load (
      LPPERSISTFILE       lpThis,
      LPCOLESTR           lpszFileName,
      DWORD               grfMode
)
{
   LPOLEDOC lpOleDoc = ((struct CDocPersistFileImpl FAR*)lpThis)->lpOleDoc;
   SCODE sc;

   OLEDBG_BEGIN2("OleDoc_PFile_Load\r\n")

   /* OLE2NOTE: grfMode passed from the caller indicates if the caller
   **    needs Read or ReadWrite permissions. if appropriate the
   **    callee should open the file with the requested permissions.
   **    the caller will normally not impose sharing permissions.
   **
   **    the sample code currently always opens its file ReadWrite.
   */

   if (OutlineDoc_LoadFromFile((LPOUTLINEDOC)lpOleDoc, (LPOLESTR)lpszFileName))
      sc = S_OK;
   else
      sc = E_FAIL;

   OLEDBG_END2
   return ResultFromScode(sc);
}


// IPersistFile::Save
STDMETHODIMP OleDoc_PFile_Save (
      LPPERSISTFILE       lpThis,
      LPCOLESTR           lpszFileName,
      BOOL                fRemember
)
{
   LPOLEDOC lpOleDoc = ((struct CDocPersistFileImpl FAR*)lpThis)->lpOleDoc;
   LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC)lpOleDoc;
   SCODE sc;

   OLEDBG_BEGIN2("OleDoc_PFile_Save\r\n")

   /* OLE2NOTE: it is only legal to perform a Save or SaveAs operation
   **    on a file-based document. if the document is an embedded
   **    object then we can not be changed to a file-base object.
   **
   **      fRemember   lpszFileName     Type of Save
   **    ----------------------------------------------
   **        TRUE        NULL           SAVE
   **        TRUE        ! NULL         SAVE AS
   **        FALSE       ! NULL         SAVE COPY AS
   **        FALSE       NULL           ***error***
   */
   if ( (lpszFileName==NULL || (lpszFileName != NULL && fRemember))
         && ((lpOutlineDoc->m_docInitType != DOCTYPE_FROMFILE
            && lpOutlineDoc->m_docInitType != DOCTYPE_NEW)) ) {
      OLEDBG_END2
      return ResultFromScode(E_INVALIDARG);
   }

   if (OutlineDoc_SaveToFile(
         (LPOUTLINEDOC)lpOleDoc,
         lpszFileName,
         lpOutlineDoc->m_cfSaveFormat,
         fRemember)) {
      sc = S_OK;
   } else
      sc = E_FAIL;

   OLEDBG_END2
   return ResultFromScode(sc);
}


// IPersistFile::SaveCompleted
STDMETHODIMP OleDoc_PFile_SaveCompleted (
      LPPERSISTFILE       lpThis,
      LPCOLESTR           lpszFileName
)
{
   LPOLEDOC lpOleDoc = ((struct CDocPersistFileImpl FAR*)lpThis)->lpOleDoc;
   LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC)lpOleDoc;

   OleDbgOut2("OleDoc_PFile_SaveCompleted\r\n");

   /* This method is called after IPersistFile::Save is called. during
   **    the period between Save and SaveCompleted the object must
   **    consider itself in NOSCRIBBLE mode (ie. it is NOT allowed to
   **    write to its file. here the object can clear its NOSCRIBBLE
   **    mode flag. the outline app never scribbles to its storage, so
   **    we have nothing to do.
   */
   return NOERROR;
}


// IPersistFile::GetCurFile
STDMETHODIMP OleDoc_PFile_GetCurFile (
      LPPERSISTFILE   lpThis,
      LPOLESTR FAR*   lplpszFileName
)
{
   LPOLEDOC lpOleDoc = ((struct CDocPersistFileImpl FAR*)lpThis)->lpOleDoc;
   LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC)lpOleDoc;
   LPMALLOC lpMalloc;
   LPOLESTR lpsz;
   SCODE sc;
   char  szAnsiStr[256];

   OleDbgOut2("OleDoc_PFile_GetCurFile\r\n");

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

   /*********************************************************************
   ** OLE2NOTE: memory returned for the lplpszFileName must be
   **    allocated appropriately using the current registered IMalloc
   **    interface. the allows the ownership of the memory to be
   **    passed to the caller (even if in another process).
   *********************************************************************/

   CoGetMalloc(MEMCTX_TASK, &lpMalloc);
   if (! lpMalloc) {

      return ResultFromScode(E_FAIL);
   }

   if (lpOutlineDoc->m_docInitType == DOCTYPE_FROMFILE) {
      /* valid filename associated; return file name */
      lpsz = (LPOLESTR)lpMalloc->lpVtbl->Alloc(
            lpMalloc,
            (OLESTRLEN(lpOutlineDoc->m_szFileName)+1)*sizeof(OLECHAR)
      );
      if (! lpsz) {
         sc = E_OUTOFMEMORY;
         goto error;
      }

      OLESTRCPY(lpsz, (LPOLESTR)lpOutlineDoc->m_szFileName);
      sc = S_OK;
   } else {
      /* no file associated; return default file name prompt */
      lpsz=(LPOLESTR)lpMalloc->lpVtbl->Alloc(lpMalloc, (lstrlen(DEFEXTENSION)+3)*sizeof(OLECHAR));
      wsprintf(szAnsiStr, "*.%s", DEFEXTENSION);
      A2W (szAnsiStr, lpsz, OLEUI_CCHPATHMAX);
      sc = S_FALSE;
   }

error:
   OleStdRelease((LPUNKNOWN)lpMalloc);
   *lplpszFileName = lpsz;


   return ResultFromScode(sc);
}


/*************************************************************************
** OleDoc::IOleItemContainer interface implementation
*************************************************************************/

// IOleItemContainer::QueryInterface
STDMETHODIMP OleDoc_ItemCont_QueryInterface(
      LPOLEITEMCONTAINER  lpThis,
      REFIID              riid,
      LPVOID FAR*         lplpvObj
)
{
   LPOLEDOC lpOleDoc =
         ((struct CDocOleItemContainerImpl FAR*)lpThis)->lpOleDoc;

   return OleDoc_QueryInterface(lpOleDoc, riid, lplpvObj);
}


// IOleItemContainer::AddRef
STDMETHODIMP_(ULONG) OleDoc_ItemCont_AddRef(LPOLEITEMCONTAINER lpThis)
{
   LPOLEDOC lpOleDoc =
         ((struct CDocOleItemContainerImpl FAR*)lpThis)->lpOleDoc;

   OleDbgAddRefMethod(lpThis, "IOleItemContainer");

   return OleDoc_AddRef((LPOLEDOC)lpOleDoc);
}


// IOleItemContainer::Release
STDMETHODIMP_(ULONG) OleDoc_ItemCont_Release(LPOLEITEMCONTAINER lpThis)
{
   LPOLEDOC lpOleDoc =
         ((struct CDocOleItemContainerImpl FAR*)lpThis)->lpOleDoc;

   OleDbgReleaseMethod(lpThis, "IOleItemContainer");

   return OleDoc_Release((LPOLEDOC)lpOleDoc);
}


// IOleItemContainer::ParseDisplayName
STDMETHODIMP OleDoc_ItemCont_ParseDisplayName(
      LPOLEITEMCONTAINER  lpThis,
      LPBC                lpbc,
      LPOLESTR            lpszDisplayName,
      ULONG FAR*          lpchEaten,
      LPMONIKER FAR*      lplpmkOut
)
{
   LPOLEDOC lpOleDoc =
         ((struct CDocOleItemContainerImpl FAR*)lpThis)->lpOleDoc;
   OLECHAR szItemName[MAXNAMESIZE];
   LPUNKNOWN lpUnk;
   HRESULT hrErr;

   OleDbgOut2("OleDoc_ItemCont_ParseDisplayName\r\n");

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

   *lpchEaten = OleStdGetItemToken(
         lpszDisplayName,
         szItemName,
         MAXNAMESIZE*sizeof(OLECHAR)
   );

   /* OLE2NOTE: get a pointer to a running instance of the object. we
   **    should force the object to go running if necessary (even if
   **    this means launching its server EXE). this is the meaining of
   **    BINDSPEED_INDEFINITE. Parsing a Moniker is known to be an
   **    "EXPENSIVE" operation.
   */
   hrErr = OleDoc_ItemCont_GetObject(
         lpThis,
         szItemName,
         BINDSPEED_INDEFINITE,
         lpbc,
         &IID_IUnknown,
         (LPVOID FAR*)&lpUnk
   );

   if (hrErr == NOERROR) {
      OleStdRelease(lpUnk);   // item name FOUND; don't need obj ptr.
      CreateItemMoniker(OLESTDDELIM, szItemName, lplpmkOut);
   } else
      *lpchEaten = 0;     // item name is NOT valid

   return hrErr;
}


// IOleItemContainer::EnumObjects
STDMETHODIMP OleDoc_ItemCont_EnumObjects(
      LPOLEITEMCONTAINER  lpThis,
      DWORD               grfFlags,
      LPENUMUNKNOWN FAR*  lplpenumUnknown
)
{
   LPOLEDOC lpOleDoc =
         ((struct CDocOleItemContainerImpl FAR*)lpThis)->lpOleDoc;

   OLEDBG_BEGIN2("OleDoc_ItemCont_EnumObjects\r\n")

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

   /* OLE2NOTE: this method should be implemented to allow programatic
   **    clients the ability to what elements the container holds.
   **    this method is NOT called in the standard linking scenarios.
   **
   **    grfFlags can be one of the following:
   **        OLECONTF_EMBEDDINGS   -- enumerate embedded objects
   **        OLECONTF_LINKS        -- enumerate linked objects
   **        OLECONTF_OTHERS       -- enumerate non-OLE compound doc objs
   **        OLECONTF_ONLYUSER     -- enumerate only objs named by user
   **        OLECONTF_ONLYIFRUNNING-- enumerate only objs in running state
   */

   OleDbgAssertSz(0, "NOT YET IMPLEMENTED!");

   OLEDBG_END2

   return ResultFromScode(E_NOTIMPL);
}


// IOleItemContainer::LockContainer
STDMETHODIMP OleDoc_ItemCont_LockContainer(
      LPOLEITEMCONTAINER  lpThis,
      BOOL                fLock
)
{
   LPOLEDOC lpOleDoc =
         ((struct CDocOleItemContainerImpl FAR*)lpThis)->lpOleDoc;
   HRESULT hrErr;
   OLEDBG_BEGIN2("OleDoc_ItemCont_LockContainer\r\n")

#if defined( _DEBUG )
   if (fLock) {
      ++lpOleDoc->m_cCntrLock;
      OleDbgOutRefCnt3(
            "OleDoc_ItemCont_LockContainer: cLock++\r\n",
            lpOleDoc,
            lpOleDoc->m_cCntrLock
      );
   } else {
      /* OLE2NOTE: when there are no open documents and the app is not
      **    under the control of the user and there are no outstanding
      **    locks on the app, then revoke our ClassFactory to enable the
      **    app to shut down.
      */
      --lpOleDoc->m_cCntrLock;
      OleDbgAssertSz (
            lpOleDoc->m_cCntrLock >= 0,
            "OleDoc_ItemCont_LockContainer(FALSE) called with cLock == 0"
      );

      if (lpOleDoc->m_cCntrLock == 0) {
         OleDbgOutRefCnt2(
               "OleDoc_ItemCont_LockContainer: UNLOCKED\r\n",
               lpOleDoc, lpOleDoc->m_cCntrLock);
      } else {
         OleDbgOutRefCnt3(
               "OleDoc_ItemCont_LockContainer: cLock--\r\n",
               lpOleDoc, lpOleDoc->m_cCntrLock);
      }
   }
#endif  // _DEBUG

   /* OLE2NOTE: in order to hold the document alive we call
   **    CoLockObjectExternal to add a strong reference to our Doc
   **    object. this will keep the Doc alive when all other external
   **    references release us. whenever an embedded object goes
   **    running a LockContainer(TRUE) is called. when the embedded
   **    object shuts down (ie. transitions from running to loaded)
   **    LockContainer(FALSE) is called. if the user issues File.Close
   **    the document will shut down in any case ignoring any
   **    outstanding LockContainer locks because CoDisconnectObject is
   **    called in OleDoc_Close. this will forceably break any
   **    existing strong reference counts including counts that we add
   **    ourselves by calling CoLockObjectExternal and guarantee that
   **    the Doc object gets its final release (ie. cRefs goes to 0).
   */
   hrErr = OleDoc_Lock(lpOleDoc, fLock, TRUE /* fLastUnlockReleases */);

   OLEDBG_END2
   return hrErr;
}


// IOleItemContainer::GetObject
STDMETHODIMP OleDoc_ItemCont_GetObject(
      LPOLEITEMCONTAINER  lpThis,
      LPOLESTR            lpszItem,
      DWORD               dwSpeedNeeded,
      LPBINDCTX           lpbc,
      REFIID              riid,
      LPVOID FAR*         lplpvObject
)
{
   LPOLEDOC lpOleDoc =
         ((struct CDocOleItemContainerImpl FAR*)lpThis)->lpOleDoc;
   HRESULT hrErr;

   OLEDBG_BEGIN2("OleDoc_ItemCont_GetObject\r\n")

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

#if defined( OLE_SERVER )

   /* OLE2NOTE: SERVER ONLY version should return PseudoObjects with
   **    BINDSPEED_IMMEDIATE, thus the dwSpeedNeeded is not important
   **    in the case of a pure server.
   */
   hrErr = ServerDoc_GetObject(
         (LPSERVERDOC)lpOleDoc, lpszItem,riid,lplpvObject);
#endif
#if defined( OLE_CNTR )

   /* OLE2NOTE: dwSpeedNeeded indicates how long the caller is willing
   **    to wait for us to get the object:
   **      BINDSPEED_IMMEDIATE -- only if obj already loaded && IsRunning
   **      BINDSPEED_MODERATE  -- load obj if necessary && if IsRunning
   **      BINDSPEED_INDEFINITE-- force obj to load and run if necessary
   */
   hrErr = ContainerDoc_GetObject(
         (LPCONTAINERDOC)lpOleDoc,lpszItem,dwSpeedNeeded,riid,lplpvObject);
#endif

   OLEDBG_END2

   return hrErr;
}


// IOleItemContainer::GetObjectStorage
STDMETHODIMP OleDoc_ItemCont_GetObjectStorage(
      LPOLEITEMCONTAINER  lpThis,
      LPOLESTR            lpszItem,
      LPBINDCTX           lpbc,
      REFIID              riid,
      LPVOID FAR*         lplpvStorage
)
{
   LPOLEDOC lpOleDoc =
         ((struct CDocOleItemContainerImpl FAR*)lpThis)->lpOleDoc;
   OleDbgOut2("OleDoc_ItemCont_GetObjectStorage\r\n");

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

#if defined( OLE_SERVER )
   /* OLE2NOTE: in the SERVER ONLY version, item names identify pseudo
   **    objects. pseudo objects, do NOT have identifiable storage.
   */
   return ResultFromScode(E_FAIL);
#endif
#if defined( OLE_CNTR )
   // We can only return an IStorage* type pointer
   if (! IsEqualIID(riid, &IID_IStorage))
   {
      return ResultFromScode(E_FAIL);
   }

   return ContainerDoc_GetObjectStorage(
         (LPCONTAINERDOC)lpOleDoc,
         lpszItem,
         (LPSTORAGE FAR*)lplpvStorage
   );
#endif
}


// IOleItemContainer::IsRunning
STDMETHODIMP OleDoc_ItemCont_IsRunning(
      LPOLEITEMCONTAINER  lpThis,
      LPOLESTR            lpszItem
)
{
   LPOLEDOC lpOleDoc =
         ((struct CDocOleItemContainerImpl FAR*)lpThis)->lpOleDoc;
   HRESULT hrErr;

   OLEDBG_BEGIN2("OleDoc_ItemCont_IsRunning\r\n")

   /* OLE2NOTE: Check if item name is valid. if so then return if
   **    Object is running. PseudoObjects in the Server version are
   **    always considered running. Ole objects in the container must
   **    be checked if they are running.
   */

#if defined( OLE_SERVER )
   hrErr = ServerDoc_IsRunning((LPSERVERDOC)lpOleDoc, lpszItem);
#endif
#if defined( OLE_CNTR )
   hrErr = ContainerDoc_IsRunning((LPCONTAINERDOC)lpOleDoc, lpszItem);
#endif

   OLEDBG_END2
   return hrErr;
}


/*************************************************************************
** OleDoc::IExternalConnection interface implementation
*************************************************************************/

// IExternalConnection::QueryInterface
STDMETHODIMP OleDoc_ExtConn_QueryInterface(
      LPEXTERNALCONNECTION    lpThis,
      REFIID                  riid,
      LPVOID FAR*             lplpvObj
)
{
   LPOLEDOC lpOleDoc =
         ((struct CDocExternalConnectionImpl FAR*)lpThis)->lpOleDoc;

   return OleDoc_QueryInterface(lpOleDoc, riid, lplpvObj);
}


// IExternalConnection::AddRef
STDMETHODIMP_(ULONG) OleDoc_ExtConn_AddRef(LPEXTERNALCONNECTION lpThis)
{
   LPOLEDOC lpOleDoc =
         ((struct CDocExternalConnectionImpl FAR*)lpThis)->lpOleDoc;

   OleDbgAddRefMethod(lpThis, "IExternalConnection");

   return OleDoc_AddRef(lpOleDoc);
}


// IExternalConnection::Release
STDMETHODIMP_(ULONG) OleDoc_ExtConn_Release (LPEXTERNALCONNECTION lpThis)
{
   LPOLEDOC lpOleDoc =
         ((struct CDocExternalConnectionImpl FAR*)lpThis)->lpOleDoc;

   OleDbgReleaseMethod(lpThis, "IExternalConnection");

   return OleDoc_Release(lpOleDoc);
}


// IExternalConnection::AddConnection
STDMETHODIMP_(DWORD) OleDoc_ExtConn_AddConnection(
      LPEXTERNALCONNECTION    lpThis,
      DWORD                   extconn,
      DWORD                   reserved
)
{
   LPOLEDOC lpOleDoc =
         ((struct CDocExternalConnectionImpl FAR*)lpThis)->lpOleDoc;

   if( extconn & EXTCONN_STRONG ) {

#if defined( _DEBUG )
      OleDbgOutRefCnt3(
            "OleDoc_ExtConn_AddConnection: dwStrongExtConn++\r\n",
            lpOleDoc,
            lpOleDoc->m_dwStrongExtConn + 1
      );
#endif
      return ++(lpOleDoc->m_dwStrongExtConn);
   } else
   {
      return 0;
   }
}


// IExternalConnection::ReleaseConnection
STDMETHODIMP_(DWORD) OleDoc_ExtConn_ReleaseConnection(
      LPEXTERNALCONNECTION    lpThis,
      DWORD                   extconn,
      DWORD                   reserved,
      BOOL                    fLastReleaseCloses
)
{
   LPOLEDOC lpOleDoc =
         ((struct CDocExternalConnectionImpl FAR*)lpThis)->lpOleDoc;

   if( extconn & EXTCONN_STRONG ){
      DWORD dwSave = --(lpOleDoc->m_dwStrongExtConn);
#if defined( _DEBUG )
      OLEDBG_BEGIN2( (fLastReleaseCloses ?
                  "OleDoc_ExtConn_ReleaseConnection(TRUE)\r\n" :
                  "OleDoc_ExtConn_ReleaseConnection(FALSE)\r\n") )
      OleDbgOutRefCnt3(
            "OleDoc_ExtConn_ReleaseConnection: dwStrongExtConn--\r\n",
            lpOleDoc,
            lpOleDoc->m_dwStrongExtConn
      );
      OleDbgAssertSz (
            lpOleDoc->m_dwStrongExtConn >= 0,
            "OleDoc_ExtConn_ReleaseConnection called with dwStrong == 0"
      );
#endif  // _DEBUG

      if( lpOleDoc->m_dwStrongExtConn == 0 && fLastReleaseCloses )
         OleDoc_Close(lpOleDoc, OLECLOSE_SAVEIFDIRTY);

      OLEDBG_END2
      return dwSave;
   } else
   {
      return 0;
   }
}


/*************************************************************************
** OleDoc Common Support Functions
*************************************************************************/


/* OleDoc_GetFullMoniker
** ---------------------
**    Return the full, absolute moniker of the document.
**
**    NOTE: the caller must release the pointer returned when done.
*/
LPMONIKER OleDoc_GetFullMoniker(LPOLEDOC lpOleDoc, DWORD dwAssign)
{
   LPMONIKER lpMoniker = NULL;

   OLEDBG_BEGIN3("OleDoc_GetFullMoniker\r\n")

   if (lpOleDoc->m_lpSrcDocOfCopy) {
      /* CASE I: this document was created for a copy or drag/drop
      **    operation. generate the moniker which identifies the
      **    source document of the original copy.
      */
      if (! lpOleDoc->m_fLinkSourceAvail)
         goto done;        // we already know a moniker is not available

      lpMoniker=OleDoc_GetFullMoniker(lpOleDoc->m_lpSrcDocOfCopy, dwAssign);
   }
   else if (lpOleDoc->m_lpFileMoniker) {

      /* CASE II: this document is a top-level user document (either
      **    file-based or untitled). return the FileMoniker stored
      **    with the document; it uniquely identifies the document.
      */
      // we must AddRef the moniker to pass out a ptr
      lpOleDoc->m_lpFileMoniker->lpVtbl->AddRef(lpOleDoc->m_lpFileMoniker);

      lpMoniker = lpOleDoc->m_lpFileMoniker;
   }

#if defined( OLE_SERVER )

   else if (((LPSERVERDOC)lpOleDoc)->m_lpOleClientSite) {

      /* CASE III: this document is an embedded object, ask our
      **    container for our moniker.
      */
      OLEDBG_BEGIN2("IOleClientSite::GetMoniker called\r\n");
      ((LPSERVERDOC)lpOleDoc)->m_lpOleClientSite->lpVtbl->GetMoniker(
            ((LPSERVERDOC)lpOleDoc)->m_lpOleClientSite,
            dwAssign,
            OLEWHICHMK_OBJFULL,
            &lpMoniker
      );
      OLEDBG_END2
   }

#endif

   else {
      lpMoniker = NULL;
   }

done:
   OLEDBG_END3
   return lpMoniker;
}


/* OleDoc_DocRenamedUpdate
** -----------------------
**    Update the documents registration in the running object table (ROT).
**    Also inform all embedded OLE objects (container only) and/or psedudo
**    objects (server only) that the name of the document has changed.
*/
void OleDoc_DocRenamedUpdate(LPOLEDOC lpOleDoc, LPMONIKER lpmkDoc)
{
   OLEDBG_BEGIN3("OleDoc_DocRenamedUpdate\r\n")

   OleDoc_AddRef(lpOleDoc);

   /* OLE2NOTE: we must re-register ourselves as running when we
   **    get a new moniker assigned (ie. when we are renamed).
   */
   OLEDBG_BEGIN3("OleStdRegisterAsRunning called\r\n")
   OleStdRegisterAsRunning(
         (LPUNKNOWN)&lpOleDoc->m_Unknown,
         lpmkDoc,
         &lpOleDoc->m_dwRegROT
   );
   OLEDBG_END3

#if defined( OLE_SERVER )
   {
      LPSERVERDOC lpServerDoc = (LPSERVERDOC)lpOleDoc;
      LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC)lpOleDoc;

      /* OLE2NOTE: inform any linking clients that the document has been
      **    renamed.
      */
      ServerDoc_SendAdvise (
            lpServerDoc,
            OLE_ONRENAME,
            lpmkDoc,
            0        /* advf -- not relevant here */
      );

      /* OLE2NOTE: inform any clients of pseudo objects
      **    within our document, that our document's
      **    Moniker has changed.
      */
      ServerNameTable_InformAllPseudoObjectsDocRenamed(
            (LPSERVERNAMETABLE)lpOutlineDoc->m_lpNameTable, lpmkDoc);
   }
#endif
#if defined( OLE_CNTR )
   {
      LPCONTAINERDOC lpContainerDoc = (LPCONTAINERDOC)lpOleDoc;

      /* OLE2NOTE: must tell all OLE objects that our container
      **    moniker changed.
      */
      ContainerDoc_InformAllOleObjectsDocRenamed(
            lpContainerDoc,
            lpmkDoc
      );
   }
#endif

   OleDoc_Release(lpOleDoc);       // release artificial AddRef above
   OLEDBG_END3
}



#if defined( OLE_SERVER )

/*************************************************************************
** ServerDoc Supprt Functions Used by Server versions
*************************************************************************/


/* ServerDoc_PseudoObjLockDoc
** --------------------------
**    Add a lock on the Doc on behalf of the PseudoObject. the Doc may not
**    close while the Doc exists.
**
**    when a pseudo object is first created, it calls this method to
**    guarantee that the document stays alive (PseudoObj_Init).
**    when a pseudo object is destroyed, it call
**    ServerDoc_PseudoObjUnlockDoc to release this hold on the document.
*/
void ServerDoc_PseudoObjLockDoc(LPSERVERDOC lpServerDoc)
{
   LPOLEDOC lpOleDoc = (LPOLEDOC)lpServerDoc;
   ULONG cPseudoObj;

   cPseudoObj = ++lpServerDoc->m_cPseudoObj;

#if defined( _DEBUG )
   OleDbgOutRefCnt3(
         "ServerDoc_PseudoObjLockDoc: cPseudoObj++\r\n",
         lpServerDoc,
         cPseudoObj
   );
#endif
   OleDoc_Lock(lpOleDoc, TRUE /* fLock */, 0 /* not applicable */);
   return;
}


/* ServerDoc_PseudoObjUnlockDoc
** ----------------------------
**    Release the lock on the Doc on behalf of the PseudoObject. if this was
**    the last lock on the Doc, then it will shutdown.
*/
void ServerDoc_PseudoObjUnlockDoc(
      LPSERVERDOC         lpServerDoc,
      LPPSEUDOOBJ         lpPseudoObj
)
{
   ULONG cPseudoObj;
   LPOLEDOC lpOleDoc = (LPOLEDOC)lpServerDoc;
   OLEDBG_BEGIN3("ServerDoc_PseudoObjUnlockDoc\r\n")

   /* OLE2NOTE: when there are no active pseudo objects in the Doc and
   **    the Doc is not visible, and if there are no outstanding locks
   **    on the Doc, then this is a "silent update"
   **    situation. our Doc is being used programatically by some
   **    client; it is NOT accessible to the user because it is
   **    NOT visible. thus since all Locks have been released, we
   **    will close the document. if the app is only running due
   **    to the presence of this document, then the app will now
   **    also shut down.
   */
   cPseudoObj = --lpServerDoc->m_cPseudoObj;

#if defined( _DEBUG )
   OleDbgAssertSz (
         lpServerDoc->m_cPseudoObj >= 0,
         "PseudoObjUnlockDoc called with cPseudoObj == 0"
   );

   OleDbgOutRefCnt3(
         "ServerDoc_PseudoObjUnlockDoc: cPseudoObj--\r\n",
         lpServerDoc,
         cPseudoObj
   );
#endif
   OleDoc_Lock(lpOleDoc, FALSE /* fLock */, TRUE /* fLastUnlockReleases */);

   OLEDBG_END3
   return;
}


/* ServerDoc_GetObject
** -------------------
**
**    Return a pointer to an object identified by an item string
**    (lpszItem). For a server-only app, the object returned will be a
**    pseudo object.
*/
HRESULT ServerDoc_GetObject(
      LPSERVERDOC             lpServerDoc,
      LPOLESTR                lpszItem,
      REFIID                  riid,
      LPVOID FAR*             lplpvObject
)
{
   LPPSEUDOOBJ lpPseudoObj;
   LPSERVERNAMETABLE lpServerNameTable =
         (LPSERVERNAMETABLE)((LPOUTLINEDOC)lpServerDoc)->m_lpNameTable;

   *lplpvObject = NULL;

   /* Get the PseudoObj which corresponds to an item name. if the item
   **    name does NOT exist in the name table then NO object is
   **    returned. the ServerNameTable_GetPseudoObj routine finds a
   **    name entry corresponding to the item name, it then checks if
   **    a PseudoObj has already been allocated. if so, it returns the
   **    existing object, otherwise it allocates a new PseudoObj.
   */
   lpPseudoObj = ServerNameTable_GetPseudoObj(
         lpServerNameTable,
         lpszItem,
         lpServerDoc
   );

   if (! lpPseudoObj) {
      *lplpvObject = NULL;
      return ResultFromScode(MK_E_NOOBJECT);
   }

   // return the desired interface pointer of the pseudo object.
   return PseudoObj_QueryInterface(lpPseudoObj, riid, lplpvObject);
}


/* ServerDoc_IsRunning
** -------------------
**
**    Check if the object identified by an item string (lpszItem) is in
**    the running state. For a server-only app, if the item name exists in
**    in the NameTable then the item name is considered running.
**    IOleItemContainer::GetObject would succeed.
*/

HRESULT ServerDoc_IsRunning(LPSERVERDOC lpServerDoc, LPOLESTR lpszItem)
{
   LPOUTLINENAMETABLE lpOutlineNameTable =
         ((LPOUTLINEDOC)lpServerDoc)->m_lpNameTable;
   LPSERVERNAME lpServerName;

   lpServerName = (LPSERVERNAME)OutlineNameTable_FindName(
         lpOutlineNameTable,
         lpszItem
   );

   if (lpServerName)
      return NOERROR;
   else
      return ResultFromScode(MK_E_NOOBJECT);
}


/* ServerDoc_GetSelRelMoniker
** --------------------------
**    Retrieve the relative item moniker which identifies the given
**    selection (lplrSel).
**
**    Returns NULL if a moniker can NOT be created.
*/

LPMONIKER ServerDoc_GetSelRelMoniker(
      LPSERVERDOC             lpServerDoc,
      LPLINERANGE             lplrSel,
      DWORD                   dwAssign
)
{
   LPOUTLINEAPP lpOutlineApp = (LPOUTLINEAPP)g_lpApp;
   LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC)lpServerDoc;
   LPSERVERNAMETABLE lpServerNameTable =
         (LPSERVERNAMETABLE)lpOutlineDoc->m_lpNameTable;
   LPOUTLINENAMETABLE lpOutlineNameTable =
         (LPOUTLINENAMETABLE)lpServerNameTable;
   LPOUTLINENAME lpOutlineName;
   LPMONIKER lpmk;

   lpOutlineName=OutlineNameTable_FindNamedRange(lpOutlineNameTable,lplrSel);

   if (lpOutlineName) {
      /* the selection range already has a name assigned */
      CreateItemMoniker(OLESTDDELIM, lpOutlineName->m_szName, &lpmk);
   } else {
      char szbuf[MAXNAMESIZE];
      OLECHAR szUniBuf[MAXNAMESIZE];

      switch (dwAssign) {

         case GETMONIKER_FORCEASSIGN:

            /* Force the assignment of the name. This is called when a
            **    Paste Link actually occurs. At this point we want to
            **    create a Name and add it to the NameTable in order to
            **    track the source of the link. This name (as all
            **    names) will be updated upon editing of the document.
            */
            wsprintf(
                  szbuf,
                  "%s %ld",
                  (LPSTR)DEFRANGENAMEPREFIX,
                  ++(lpServerDoc->m_nNextRangeNo)
            );

            lpOutlineName = OutlineApp_CreateName(lpOutlineApp);

            if (lpOutlineName) {
               A2W (szbuf, lpOutlineName->m_szName, OLEUI_CCHPATHMAX);
               lpOutlineName->m_nStartLine = lplrSel->m_nStartLine;
               lpOutlineName->m_nEndLine = lplrSel->m_nEndLine;
               OutlineDoc_AddName(lpOutlineDoc, lpOutlineName);
            } else {
               // REVIEW: do we need "Out-of-Memory" error message here?
            }
            break;

         case GETMONIKER_TEMPFORUSER:

            /* Create a name to show to the user in the Paste
            **    Special dialog but do NOT yet incur the overhead
            **    of adding a Name to the NameTable. The Moniker
            **    generated should be useful to display to the user
            **    to indicate the source of the copy, but will NOT
            **    be used to create a link directly (the caller
            **    should ask again for a moniker specifying FORCEASSIGN).
            **    we will generate the name that would be the next
            **    auto-generated range name, BUT will NOT actually
            **    increment the range counter.
            */
            wsprintf(
                  szbuf,
                  "%s %ld",
                  (LPSTR)DEFRANGENAMEPREFIX,
                  (lpServerDoc->m_nNextRangeNo)+1
            );
            break;

         case GETMONIKER_ONLYIFTHERE:

            /* the caller only wants a name if one has already been
            **    assigned. we have already above checked if the
            **    current selection has a name, so we will simply
            **    return NULL here.
            */
            return NULL;    // no moniker is assigned

         default:
            return NULL;    // unknown flag given
      }

      A2W (szbuf, szUniBuf, MAXNAMESIZE);
      CreateItemMoniker(OLESTDDELIM, szUniBuf, &lpmk);
   }

   return lpmk;
}


/* ServerDoc_GetSelFullMoniker
** ---------------------------
**    Retrieve the full absolute moniker which identifies the given
**    selection (lplrSel).
**    this moniker is created as a composite of the absolute moniker for
**    the entire document appended with an item moniker which identifies
**    the selection relative to the document.
**    Returns NULL if a moniker can NOT be created.
*/
LPMONIKER ServerDoc_GetSelFullMoniker(
      LPSERVERDOC             lpServerDoc,
      LPLINERANGE             lplrSel,
      DWORD                   dwAssign
)
{
   LPMONIKER lpmkDoc = NULL;
   LPMONIKER lpmkItem = NULL;
   LPMONIKER lpmkFull = NULL;

   lpmkDoc = OleDoc_GetFullMoniker(
         (LPOLEDOC)lpServerDoc,
         dwAssign
   );
   if (! lpmkDoc) return NULL;

   lpmkItem = ServerDoc_GetSelRelMoniker(
         lpServerDoc,
         lplrSel,
         dwAssign
   );
   if (lpmkItem) {
      CreateGenericComposite(lpmkDoc, lpmkItem, (LPMONIKER FAR*)&lpmkFull);
      OleStdRelease((LPUNKNOWN)lpmkItem);
   }

   if (lpmkDoc)
      OleStdRelease((LPUNKNOWN)lpmkDoc);

   return lpmkFull;
}


/* ServerNameTable_EditLineUpdate
 * -------------------------------
 *
 *      Update the table when a line at nEditIndex is edited.
 */
void ServerNameTable_EditLineUpdate(
      LPSERVERNAMETABLE       lpServerNameTable,
      int                     nEditIndex
)
{
   LPOUTLINENAMETABLE lpOutlineNameTable =
                              (LPOUTLINENAMETABLE)lpServerNameTable;
   LPOUTLINENAME lpOutlineName;
   LINERANGE lrSel;
   LPPSEUDOOBJ lpPseudoObj;
   int i;

   for(i = 0; i < lpOutlineNameTable->m_nCount; i++) {
      lpOutlineName=OutlineNameTable_GetName(lpOutlineNameTable, i);

      lpPseudoObj = ((LPSERVERNAME)lpOutlineName)->m_lpPseudoObj;

      /* if there is a pseudo object associated with this name, then
      **    check if the line that was modified is included within
      **    the named range.
      */
      if (lpPseudoObj) {
         OutlineName_GetSel(lpOutlineName, &lrSel);

         if(((int)lrSel.m_nStartLine <= nEditIndex) &&
            ((int)lrSel.m_nEndLine >= nEditIndex)) {

            // inform linking clients data has changed
            PseudoObj_SendAdvise(
                  lpPseudoObj,
                  OLE_ONDATACHANGE,
                  NULL,   /* lpmkDoc -- not relevant here */
                  0       /* advf -- no flags necessary */
            );
         }

      }
   }
}


/* ServerNameTable_InformAllPseudoObjectsDocRenamed
 * ------------------------------------------------
 *
 *      Inform all pseudo object clients that the name of the pseudo
 *      object has changed.
 */
void ServerNameTable_InformAllPseudoObjectsDocRenamed(
      LPSERVERNAMETABLE       lpServerNameTable,
      LPMONIKER               lpmkDoc
)
{
   LPOUTLINENAMETABLE lpOutlineNameTable =
                              (LPOUTLINENAMETABLE)lpServerNameTable;
   LPOUTLINENAME lpOutlineName;
   LPPSEUDOOBJ lpPseudoObj;
   LPMONIKER lpmkObj;
   int i;

   OLEDBG_BEGIN2("ServerNameTable_InformAllPseudoObjectsDocRenamed\r\n");

   for(i = 0; i < lpOutlineNameTable->m_nCount; i++) {
      lpOutlineName=OutlineNameTable_GetName(lpOutlineNameTable, i);

      lpPseudoObj = ((LPSERVERNAME)lpOutlineName)->m_lpPseudoObj;

      /* if there is a pseudo object associated with this name, then
      **    send OnRename advise to its linking clients.
      */
      if (lpPseudoObj &&
         ((lpmkObj=PseudoObj_GetFullMoniker(lpPseudoObj,lpmkDoc))!=NULL)) {

         // inform the clients that the name has changed
         PseudoObj_SendAdvise (
               lpPseudoObj,
               OLE_ONRENAME,
               lpmkObj,
               0           /* advf -- not relevant here */
         );
      }
   }
   OLEDBG_END2
}


/* ServerNameTable_InformAllPseudoObjectsDocSaved
 * ------------------------------------------------
 *
 *      Inform all pseudo object clients that the name of the pseudo
 *      object has changed.
 */
void ServerNameTable_InformAllPseudoObjectsDocSaved(
      LPSERVERNAMETABLE       lpServerNameTable,
      LPMONIKER               lpmkDoc
)
{
   LPOUTLINENAMETABLE lpOutlineNameTable =
                              (LPOUTLINENAMETABLE)lpServerNameTable;
   LPOUTLINENAME lpOutlineName;
   LPPSEUDOOBJ lpPseudoObj;
   LPMONIKER lpmkObj;
   int i;

   OLEDBG_BEGIN2("ServerNameTable_InformAllPseudoObjectsDocSaved\r\n");

   for(i = 0; i < lpOutlineNameTable->m_nCount; i++) {
      lpOutlineName=OutlineNameTable_GetName(lpOutlineNameTable, i);

      lpPseudoObj = ((LPSERVERNAME)lpOutlineName)->m_lpPseudoObj;

      /* if there is a pseudo object associated with this name, then
      **    send OnSave advise to its linking clients.
      */
      if (lpPseudoObj &&
         ((lpmkObj=PseudoObj_GetFullMoniker(lpPseudoObj,lpmkDoc))!=NULL)) {

         // inform the clients that the name has been saved
         PseudoObj_SendAdvise (
               lpPseudoObj,
               OLE_ONSAVE,
               NULL,   /* lpmkDoc -- not relevant here */
               0       /* advf -- not relevant here */
         );
      }
   }
   OLEDBG_END2
}


/* ServerNameTable_SendPendingAdvises
 * ----------------------------------
 *
 *      Send any pending change notifications for pseudo objects.
 *  while ReDraw is diabled on the ServerDoc, then change advise
 *  notifications are not sent to pseudo object clients.
 */
void ServerNameTable_SendPendingAdvises(LPSERVERNAMETABLE lpServerNameTable)
{
   LPOUTLINENAMETABLE lpOutlineNameTable =
                              (LPOUTLINENAMETABLE)lpServerNameTable;
   LPSERVERNAME lpServerName;
   int i;

   for(i = 0; i < lpOutlineNameTable->m_nCount; i++) {
      lpServerName = (LPSERVERNAME)OutlineNameTable_GetName(
            lpOutlineNameTable,
            i
      );
      ServerName_SendPendingAdvises(lpServerName);
   }
}


/* ServerNameTable_GetPseudoObj
** ----------------------------
**
**    Return a pointer to a pseudo object identified by an item string
**    (lpszItem). if the pseudo object already exists, then return the
**    existing object, otherwise allocate a new pseudo object.
*/
LPPSEUDOOBJ ServerNameTable_GetPseudoObj(
      LPSERVERNAMETABLE       lpServerNameTable,
      LPOLESTR                lpszItem,
      LPSERVERDOC             lpServerDoc
)
{
   LPSERVERNAME lpServerName;

   lpServerName = (LPSERVERNAME)OutlineNameTable_FindName(
         (LPOUTLINENAMETABLE)lpServerNameTable,
         lpszItem
   );

   if (lpServerName)
      return ServerName_GetPseudoObj(lpServerName, lpServerDoc);
   else
      return NULL;
}


/* ServerNameTable_CloseAllPseudoObjs
 * ----------------------------------
 *
 *  Force all pseudo objects to close. this results in sending OnClose
 *  notification to each pseudo object's linking clients.
 */
void ServerNameTable_CloseAllPseudoObjs(LPSERVERNAMETABLE lpServerNameTable)
{
   LPOUTLINENAMETABLE lpOutlineNameTable =
                              (LPOUTLINENAMETABLE)lpServerNameTable;
   LPSERVERNAME lpServerName;
   int i;

   OLEDBG_BEGIN3("ServerNameTable_CloseAllPseudoObjs\r\n")

   for(i = 0; i < lpOutlineNameTable->m_nCount; i++) {
      lpServerName = (LPSERVERNAME)OutlineNameTable_GetName(
            lpOutlineNameTable,
            i
      );
        ServerName_ClosePseudoObj(lpServerName);
   }

   OLEDBG_END3
}



/* ServerName_SetSel
 * -----------------
 *
 *      Change the line range of a  name.
 */
void ServerName_SetSel(
      LPSERVERNAME            lpServerName,
      LPLINERANGE             lplrSel,
      BOOL                    fRangeModified
)
{
   LPOUTLINENAME lpOutlineName = (LPOUTLINENAME)lpServerName;
   BOOL fPseudoObjChanged = fRangeModified;

   if (lpOutlineName->m_nStartLine != lplrSel->m_nStartLine) {
      lpOutlineName->m_nStartLine = lplrSel->m_nStartLine;
      fPseudoObjChanged = TRUE;
   }

   if (lpOutlineName->m_nEndLine != lplrSel->m_nEndLine) {
      lpOutlineName->m_nEndLine = lplrSel->m_nEndLine;
      fPseudoObjChanged = TRUE;
   }

   /* OLE2NOTE: if the range of an active pseudo object has
   **    changed, then inform any linking clients that the object
   **    has changed.
   */
   if (lpServerName->m_lpPseudoObj && fPseudoObjChanged) {
      PseudoObj_SendAdvise(
            lpServerName->m_lpPseudoObj,
            OLE_ONDATACHANGE,
            NULL,   /* lpmkDoc -- not relevant here */
            0       /* advf -- no flags necessary */
      );
   }
}


/* ServerName_SendPendingAdvises
 * -----------------------------
 *
 *      Send any pending change notifications for the associated
 *  pseudo objects for this name (if one exists).
 *  while ReDraw is diabled on the ServerDoc, then change advise
 *  notifications are not sent to pseudo object clients.
 */
void ServerName_SendPendingAdvises(LPSERVERNAME lpServerName)
{
   if (! lpServerName->m_lpPseudoObj)
      return;     // no associated pseudo object

   if (lpServerName->m_lpPseudoObj->m_fDataChanged)
      PseudoObj_SendAdvise(
            lpServerName->m_lpPseudoObj,
            OLE_ONDATACHANGE,
            NULL,   /* lpmkDoc -- not relevant here */
            0       /* advf -- no flags necessary */
      );
}


/* ServerName_GetPseudoObj
** -----------------------
**
**    Return a pointer to a pseudo object associated to a ServerName.
**    if the pseudo object already exists, then return the
**    existing object, otherwise allocate a new pseudo object.
**
**    NOTE: the PseudoObj is returned with a 0 refcnt if first created,
**    else the existing refcnt is unchanged.
*/
LPPSEUDOOBJ ServerName_GetPseudoObj(
      LPSERVERNAME            lpServerName,
      LPSERVERDOC             lpServerDoc
)
{
   // Check if a PseudoObj already exists
   if (lpServerName->m_lpPseudoObj)
      return lpServerName->m_lpPseudoObj;

   // A PseudoObj does NOT already exist, allocate a new one.
   lpServerName->m_lpPseudoObj=(LPPSEUDOOBJ) New((DWORD)sizeof(PSEUDOOBJ));
   if (lpServerName->m_lpPseudoObj == NULL) {
      OleDbgAssertSz(lpServerName->m_lpPseudoObj != NULL,   "Error allocating PseudoObj");
      return NULL;
   }

   PseudoObj_Init(lpServerName->m_lpPseudoObj, lpServerName, lpServerDoc);
   return lpServerName->m_lpPseudoObj;
}


/* ServerName_ClosePseudoObj
 * -------------------------
 *
 *      if there is an associated pseudo objects for this name (if one
 *  exists), then close it. this results in sending OnClose
 *  notification to the pseudo object's linking clients.
 */
void ServerName_ClosePseudoObj(LPSERVERNAME lpServerName)
{
   if (!lpServerName || !lpServerName->m_lpPseudoObj)
      return;     // no associated pseudo object

   PseudoObj_Close(lpServerName->m_lpPseudoObj);
}


#endif  // OLE_SERVER


#if defined( OLE_CNTR )


/*************************************************************************
** ContainerDoc Supprt Functions Used by Container versions
*************************************************************************/


/* ContainerLine_GetRelMoniker
** ---------------------------
**    Retrieve the relative item moniker which identifies the OLE object
**    relative to the container document.
**
**    Returns NULL if a moniker can NOT be created.
*/
LPMONIKER ContainerLine_GetRelMoniker(
      LPCONTAINERLINE         lpContainerLine,
      DWORD                   dwAssign
)
{
   LPMONIKER lpmk = NULL;

   /* OLE2NOTE: we should only give out a moniker for the OLE object
   **    if the object is allowed to be linked to from the inside. if
   **    so we are allowed to give out a moniker which binds to the
   **    running OLE object). if the object is an OLE 2.0 embedded
   **    object then it is allowed to be linked to from the inside. if
   **    the object is either an OleLink or an OLE 1.0 embedding
   **    then it can not be linked to from the inside.
   **    if we were a container/server app then we could offer linking
   **    to the outside of the object (ie. a pseudo object within our
   **    document). we are a container only app that does not support
   **    linking to ranges of its data.
   */

   switch (dwAssign) {

      case GETMONIKER_FORCEASSIGN:

            /* Force the assignment of the name. This is called when a
            **    Paste Link actually occurs. From now on we want
            **    to inform the OLE object that its moniker is
            **    assigned and is thus necessary to register itself
            **    in the RunningObjectTable.
            */
            CreateItemMoniker(
                  OLESTDDELIM, lpContainerLine->m_szStgName, &lpmk);

            /* OLE2NOTE: if the OLE object is already loaded and it
            **    is being assigned a moniker for the first time,
            **    then we need to inform it that it now has a moniker
            **    assigned by calling IOleObject::SetMoniker. this
            **    will force the OLE object to register in the
            **    RunningObjectTable when it enters the running
            **    state. if the object is not currently loaded,
            **    SetMoniker will be called automatically later when
            **    the object is loaded by the function
            **    ContainerLine_LoadOleObject.
            */
            if (! lpContainerLine->m_fMonikerAssigned) {

               /* we must remember forever more that this object has a
               **    moniker assigned.
               */
               lpContainerLine->m_fMonikerAssigned = TRUE;

               // we are now dirty and must be saved
               OutlineDoc_SetModified(
                     (LPOUTLINEDOC)lpContainerLine->m_lpDoc,
                     TRUE,   /* fModified */
                     FALSE,  /* fDataChanged--N/A for container ver. */
                     FALSE   /* fSizeChanged--N/A for container ver. */
               );

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

      case GETMONIKER_ONLYIFTHERE:

            /* If the OLE object currently has a moniker assigned,
            **    then return it.
            */
            if (lpContainerLine->m_fMonikerAssigned) {
               CreateItemMoniker(
                     OLESTDDELIM,
                     lpContainerLine->m_szStgName,
                     &lpmk
               );
            }
            break;

      case GETMONIKER_TEMPFORUSER:

            /* Return the moniker that would be used for the OLE
            **    object but do NOT force moniker assignment at
            **    this point. Since our strategy is to use the
            **    storage name of the object as its item name, we
            **    can simply create the corresponding ItemMoniker
            **    (indepenedent of whether the moniker is currently
            **    assigned or not).
            */
            CreateItemMoniker(
                  OLESTDDELIM,
                  lpContainerLine->m_szStgName,
                  &lpmk
            );
            break;

      case GETMONIKER_UNASSIGN:

            lpContainerLine->m_fMonikerAssigned = FALSE;
            break;

   }

   return lpmk;
}


/* ContainerLine_GetFullMoniker
** ----------------------------
**    Retrieve the full absolute moniker which identifies the OLE object
**    in the container document.
**    this moniker is created as a composite of the absolute moniker for
**    the entire document appended with an item moniker which identifies
**    the OLE object relative to the document.
**    Returns NULL if a moniker can NOT be created.
*/
LPMONIKER ContainerLine_GetFullMoniker(
      LPCONTAINERLINE         lpContainerLine,
      DWORD                   dwAssign
)
{
   LPMONIKER lpmkDoc = NULL;
   LPMONIKER lpmkItem = NULL;
   LPMONIKER lpmkFull = NULL;

   lpmkDoc = OleDoc_GetFullMoniker(
         (LPOLEDOC)lpContainerLine->m_lpDoc,
         dwAssign
   );
   if (! lpmkDoc)
   {
       return NULL;
   }
   lpmkItem = ContainerLine_GetRelMoniker(lpContainerLine, dwAssign);

   if (lpmkItem) {
      CreateGenericComposite(lpmkDoc, lpmkItem, (LPMONIKER FAR*)&lpmkFull);
      OleStdRelease((LPUNKNOWN)lpmkItem);
   }

   if (lpmkDoc)
      OleStdRelease((LPUNKNOWN)lpmkDoc);

   return lpmkFull;
}


/* ContainerDoc_InformAllOleObjectsDocRenamed
** ------------------------------------------
**    Inform all OLE objects that the name of the ContainerDoc has changed.
*/
void ContainerDoc_InformAllOleObjectsDocRenamed(
      LPCONTAINERDOC          lpContainerDoc,
      LPMONIKER               lpmkDoc
)
{
   LPLINELIST lpLL = &((LPOUTLINEDOC)lpContainerDoc)->m_LineList;
   int i;
   LPLINE lpLine;

   // artificial AddRef in case someone releases object during call
   OleDoc_AddRef((LPOLEDOC)lpContainerDoc);

   for (i = 0; i < lpLL->m_nNumLines; i++) {
      lpLine=LineList_GetLine(lpLL, i);

      if (lpLine && (Line_GetLineType(lpLine)==CONTAINERLINETYPE)) {
         LPCONTAINERLINE lpContainerLine = (LPCONTAINERLINE)lpLine;

         /* OLE2NOTE: if the OLE object is already loaded AND the
         **    object already has a moniker assigned, then we need
         **    to inform it that the moniker of the ContainerDoc has
         **    changed. of course, this means the full moniker of
         **    the object has changed. to do this we call
         **    IOleObject::SetMoniker. this will force the OLE
         **    object to re-register in the RunningObjectTable if it
         **    is currently in the running state. it is not in the
         **    running state, the object handler can make not that
         **    the object has a new moniker. if the object is not
         **    currently loaded, SetMoniker will be called
         **    automatically later when the object is loaded by the
         **    function ContainerLine_LoadOleObject.
         **    also if the object is a linked object, we always want
         **    to call SetMoniker on the link so that in case the
         **    link source is contained within our same container,
         **    the link source will be tracked. the link rebuilds
         **    its absolute moniker if it has a relative moniker.
         */
         if (lpContainerLine->m_lpOleObj) {
            if (lpContainerLine->m_fMonikerAssigned ||
               lpContainerLine->m_dwLinkType != 0) {
               OLEDBG_BEGIN2("IOleObject::SetMoniker called\r\n")
               lpContainerLine->m_lpOleObj->lpVtbl->SetMoniker(
                     lpContainerLine->m_lpOleObj,
                     OLEWHICHMK_CONTAINER,
                     lpmkDoc
               );
               OLEDBG_END2
            }

            /* OLE2NOTE: we must call IOleObject::SetHostNames so
            **    any open objects can update their window titles.
            */
            OLEDBG_BEGIN2("IOleObject::SetHostNames called\r\n")
            lpContainerLine->m_lpOleObj->lpVtbl->SetHostNames(
                  lpContainerLine->m_lpOleObj,
                  (LPOLESTR)APPNAME,
                  ((LPOUTLINEDOC)lpContainerDoc)->m_lpszDocTitle
            );
            OLEDBG_END2
         }
      }
   }
   // release artificial AddRef
   OleDoc_Release((LPOLEDOC)lpContainerDoc);
}


/* ContainerDoc_GetObject
** ----------------------
**    Return a pointer to the desired interface of an object identified
**    by an item string (lpszItem). the object returned will be an OLE
**    object (either link or embedding).
**
**      OLE2NOTE: we must force the object to run because we are
**          REQUIRED to return a pointer the OLE object in the
**          RUNNING state.
**
**    dwSpeedNeeded indicates how long the caller is willing
**    to wait for us to get the object:
**      BINDSPEED_IMMEDIATE -- only if obj already loaded && IsRunning
**      BINDSPEED_MODERATE  -- load obj if necessary && if IsRunning
**      BINDSPEED_INDEFINITE-- force obj to load and run if necessary
*/
HRESULT ContainerDoc_GetObject(
      LPCONTAINERDOC          lpContainerDoc,
      LPOLESTR                lpszItem,
      DWORD                   dwSpeedNeeded,
      REFIID                  riid,
      LPVOID FAR*             lplpvObject
)
{
   LPLINELIST  lpLL = &((LPOUTLINEDOC)lpContainerDoc)->m_LineList;
   int         i;
   LPLINE      lpLine;
   BOOL        fMatchFound = FALSE;
   DWORD       dwStatus;
   HRESULT     hrErr;

   *lplpvObject = NULL;

   for (i = 0; i < lpLL->m_nNumLines; i++) {
      lpLine=LineList_GetLine(lpLL, i);

      if (lpLine && (Line_GetLineType(lpLine)==CONTAINERLINETYPE)) {
         LPCONTAINERLINE lpContainerLine = (LPCONTAINERLINE)lpLine;

         if (OLESTRCMP(lpContainerLine->m_szStgName, lpszItem) == 0) {

            fMatchFound = TRUE;     // valid item name

            // check if object is loaded.
            if (lpContainerLine->m_lpOleObj == NULL) {

               // if BINDSPEED_IMMEDIATE is requested, object must
               // ALREADY be loadded.
               if (dwSpeedNeeded == BINDSPEED_IMMEDIATE)
                  return ResultFromScode(MK_E_EXCEEDEDDEADLINE);

               ContainerLine_LoadOleObject(lpContainerLine);
               if (! lpContainerLine->m_lpOleObj)
                  return ResultFromScode(E_OUTOFMEMORY);
            }

            /* OLE2NOTE: check if the object is allowed to be linked
            **    to from the inside (ie. we are allowed to
            **    give out a moniker which binds to the running
            **    OLE object). if the object is an OLE
            **    2.0 embedded object then it is allowed to be
            **    linked to from the inside. if the object is
            **    either an OleLink or an OLE 1.0 embedding
            **    then it can not be linked to from the inside.
            **    if we were a container/server app then we
            **    could offer linking to the outside of the
            **    object (ie. a pseudo object within our
            **    document). we are a container only app that
            **    does not support linking to ranges of its data.
            */
            OLEDBG_BEGIN2("IOleObject::GetMiscStatus called\r\n");
            lpContainerLine->m_lpOleObj->lpVtbl->GetMiscStatus(
                  lpContainerLine->m_lpOleObj,
                  DVASPECT_CONTENT, /* aspect is not important */
                  (LPDWORD)&dwStatus
            );
            OLEDBG_END2
            if (dwStatus & OLEMISC_CANTLINKINSIDE)
            {
               return ResultFromScode(MK_E_NOOBJECT);
            }

            // check if object is running.
            if (! OleIsRunning(lpContainerLine->m_lpOleObj)) {

               // if BINDSPEED_MODERATE is requested, object must
               // ALREADY be running.
               if (dwSpeedNeeded == BINDSPEED_MODERATE)
               {
                  return ResultFromScode(MK_E_EXCEEDEDDEADLINE);
               }

               /* OLE2NOTE: we have found a match for the item name.
               **    now we must return a pointer to the desired
               **    interface on the RUNNING object. we must
               **    carefully load the object and initially ask for
               **    an interface that we are sure the loaded form of
               **    the object supports. if we immediately ask the
               **    loaded object for the desired interface, the
               **    QueryInterface call might fail if it is an
               **    interface that is supported only when the object
               **    is running. thus we force the object to load and
               **    return its IUnknown*. then we force the object to
               **    run, and then finally, we can ask for the
               **    actually requested interface.
               */
               hrErr = ContainerLine_RunOleObject(lpContainerLine);
               if (hrErr != NOERROR) {
                  return hrErr;
               }
            }

            // Retrieve the requested interface
            *lplpvObject = OleStdQueryInterface(
                  (LPUNKNOWN)lpContainerLine->m_lpOleObj, riid);

            break;  // Match FOUND!
         }
      }
   }

   if (*lplpvObject != NULL) {
      return NOERROR;
   } else
   {
      return (fMatchFound ? ResultFromScode(E_NOINTERFACE)
                     : ResultFromScode(MK_E_NOOBJECT));
   }
}


/* ContainerDoc_GetObjectStorage
** -----------------------------
**    Return a pointer to the IStorage* used by the object identified
**    by an item string (lpszItem). the object identified could be either
**    an OLE object (either link or embedding).
*/
HRESULT ContainerDoc_GetObjectStorage(
      LPCONTAINERDOC          lpContainerDoc,
      LPOLESTR                lpszItem,
      LPSTORAGE FAR*          lplpStg
)
{
   LPLINELIST lpLL = &((LPOUTLINEDOC)lpContainerDoc)->m_LineList;
   int i;
   LPLINE lpLine;

   *lplpStg = NULL;

   for (i = 0; i < lpLL->m_nNumLines; i++) {
      lpLine=LineList_GetLine(lpLL, i);

      if (lpLine && (Line_GetLineType(lpLine)==CONTAINERLINETYPE)) {
         LPCONTAINERLINE lpContainerLine = (LPCONTAINERLINE)lpLine;

         if (OLESTRCMP(lpContainerLine->m_szStgName, lpszItem) == 0) {

            *lplpStg = lpContainerLine->m_lpStg;
            break;  // Match FOUND!
         }
      }
   }

   if (*lplpStg != NULL) {
      return NOERROR;
   } else
   {
      return ResultFromScode(MK_E_NOOBJECT);
   }
}


/* ContainerDoc_IsRunning
** ----------------------
**    Check if the object identified by an item string (lpszItem) is in
**    the running state.
**    For a container-only app, a check is made if the OLE object
**    associated with the item name is running.
*/
HRESULT ContainerDoc_IsRunning(LPCONTAINERDOC   lpContainerDoc, LPOLESTR lpszItem)
{
   LPLINELIST  lpLL = &((LPOUTLINEDOC)lpContainerDoc)->m_LineList;
   int         i;
   LPLINE      lpLine;
   DWORD       dwStatus;

   for (i = 0; i < lpLL->m_nNumLines; i++) {
      lpLine=LineList_GetLine(lpLL, i);

      if (lpLine && (Line_GetLineType(lpLine)==CONTAINERLINETYPE)) {
         LPCONTAINERLINE lpContainerLine = (LPCONTAINERLINE)lpLine;

         if (OLESTRCPY(lpContainerLine->m_szStgName, lpszItem) == 0) {

            /* OLE2NOTE: we have found a match for the item name.
            **    now we must check if the OLE object is running.
            **    we will load the object if not already loaded.
            */
            if (! lpContainerLine->m_lpOleObj) {
               ContainerLine_LoadOleObject(lpContainerLine);
               if (! lpContainerLine->m_lpOleObj)
                  return ResultFromScode(E_OUTOFMEMORY);
            }

            /* OLE2NOTE: check if the object is allowed to be linked
            **    to from the inside (ie. we are allowed to
            **    give out a moniker which binds to the running
            **    OLE object). if the object is an OLE
            **    2.0 embedded object then it is allowed to be
            **    linked to from the inside. if the object is
            **    either an OleLink or an OLE 1.0 embedding
            **    then it can not be linked to from the inside.
            **    if we were a container/server app then we
            **    could offer linking to the outside of the
            **    object (ie. a pseudo object within our
            **    document). we are a container only app that
            **    does not support linking to ranges of its data.
            */
            OLEDBG_BEGIN2("IOleObject::GetMiscStatus called\r\n")
            lpContainerLine->m_lpOleObj->lpVtbl->GetMiscStatus(
                  lpContainerLine->m_lpOleObj,
                  DVASPECT_CONTENT, /* aspect is not important */
                  (LPDWORD)&dwStatus
            );
            OLEDBG_END2
            if (dwStatus & OLEMISC_CANTLINKINSIDE)
            {
               return ResultFromScode(MK_E_NOOBJECT);
            }

            if (OleIsRunning(lpContainerLine->m_lpOleObj))
            {
               return NOERROR;
            }
            else
            {
               return ResultFromScode(S_FALSE);
            }
         }
      }
   }
   // no object was found corresponding to the item name
   return ResultFromScode(MK_E_NOOBJECT);
}

#endif  // OLE_CNTR
