/*************************************************************************
**
**    OLE 2 Server Sample Code
**
**    oledoc.c
**
**    This file contains general OleDoc methods and related support
**    functions. OleDoc implementation is used by both the Container
**    versions and the Server (Object) versions of the Outline Sample.
**
**    This file includes general support for the following:
**    1. show/hide doc window
**    2. QueryInterface, AddRef, Release
**    3. document locking (calls CoLockObjectExternal)
**    4. document shutdown (Close, Destroy)
**    5. clipboard support
**
**    OleDoc Object
**      exposed interfaces:
**          IUnknown
**          IPersistFile
**          IOleItemContainer
**          IDataObject
**
**    (c) Copyright Microsoft Corp. 1992 - 1993 All Rights Reserved
**
*************************************************************************/


#include "outline.h"

OLEDBGDATA

extern LPOUTLINEAPP             g_lpApp;

extern IUnknownVtbl             g_OleDoc_UnknownVtbl;
extern IPersistFileVtbl         g_OleDoc_PersistFileVtbl;
extern IOleItemContainerVtbl    g_OleDoc_OleItemContainerVtbl;
extern IExternalConnectionVtbl  g_OleDoc_ExternalConnectionVtbl;
extern IDataObjectVtbl          g_OleDoc_DataObjectVtbl;

#if defined( USE_DRAGDROP )
extern IDropTargetVtbl          g_OleDoc_DropTargetVtbl;
extern IDropSourceVtbl          g_OleDoc_DropSourceVtbl;
#endif  // USE_DRAGDROP

#if defined( INPLACE_CNTR )
extern BOOL g_fInsideOutContainer;
#endif


/* OleDoc_Init
 * -----------
 *
 *  Initialize the fields of a new OleDoc object. The object is initially
 *  not associated with a file or an (Untitled) document. This function sets
 *  the docInitType to DOCTYPE_UNKNOWN. After calling this function the
 *  caller should call:
 *      1.) Doc_InitNewFile to set the OleDoc to (Untitled)
 *      2.) Doc_LoadFromFile to associate the OleDoc with a file.
 *  This function creates a new window for the document.
 *
 *  NOTE: the window is initially created with a NIL size. it must be
 *        sized and positioned by the caller. also the document is initially
 *        created invisible. the caller must call OutlineDoc_ShowWindow
 *        after sizing it to make the document window visible.
 */
BOOL OleDoc_Init(LPOLEDOC lpOleDoc, BOOL fDataTransferDoc)
{
   LPOLEAPP   lpOleApp = (LPOLEAPP)g_lpApp;
   LPLINELIST lpLL     = (LPLINELIST)&((LPOUTLINEDOC)lpOleDoc)->m_LineList;

   lpOleDoc->m_cRef                        = 0;
   lpOleDoc->m_dwStrongExtConn             = 0;
#if defined( _DEBUG )
   lpOleDoc->m_cCntrLock                   = 0;
#endif
   lpOleDoc->m_lpStg                       = NULL;
   lpOleDoc->m_lpLLStm                     = NULL;
   lpOleDoc->m_lpNTStm                     = NULL;
   lpOleDoc->m_dwRegROT                    = 0;
   lpOleDoc->m_lpFileMoniker               = NULL;
   lpOleDoc->m_fLinkSourceAvail            = FALSE;
   lpOleDoc->m_lpSrcDocOfCopy              = NULL;
   lpOleDoc->m_fObjIsClosing               = FALSE;
   lpOleDoc->m_fObjIsDestroying            = FALSE;
   lpOleDoc->m_fUpdateEditMenu             = FALSE;

#if defined( USE_DRAGDROP )
   lpOleDoc->m_dwTimeEnterScrollArea       = 0L;
   lpOleDoc->m_dwNextScrollTime            = 0L;
   lpOleDoc->m_dwLastScrollDir             = SCROLLDIR_NULL;
   lpOleDoc->m_fRegDragDrop                = FALSE;
   lpOleDoc->m_fLocalDrag                  = FALSE;
   lpOleDoc->m_fCanDropCopy                = FALSE;
   lpOleDoc->m_fCanDropLink                = FALSE;
   lpOleDoc->m_fLocalDrop                  = FALSE;
   lpOleDoc->m_fDragLeave                  = FALSE;
   lpOleDoc->m_fPendingDrag                = FALSE;
#endif
#if defined( INPLACE_SVR ) || defined( INPLACE_CNTR )
   lpOleDoc->m_fCSHelpMode                 = FALSE;    // Shift-F1 context
                                          // sensitive help mode
#endif

   INIT_INTERFACEIMPL(
         &lpOleDoc->m_Unknown,
         &g_OleDoc_UnknownVtbl,
         lpOleDoc
   );

   INIT_INTERFACEIMPL(
         &lpOleDoc->m_PersistFile,
         &g_OleDoc_PersistFileVtbl,
         lpOleDoc
   );

   INIT_INTERFACEIMPL(
         &lpOleDoc->m_OleItemContainer,
         &g_OleDoc_OleItemContainerVtbl,
         lpOleDoc
   );

   INIT_INTERFACEIMPL(
         &lpOleDoc->m_ExternalConnection,
         &g_OleDoc_ExternalConnectionVtbl,
         lpOleDoc
   );

   INIT_INTERFACEIMPL(
         &lpOleDoc->m_DataObject,
         &g_OleDoc_DataObjectVtbl,
         lpOleDoc
   );

#if defined( USE_DRAGDROP )
   INIT_INTERFACEIMPL(
         &lpOleDoc->m_DropSource,
         &g_OleDoc_DropSourceVtbl,
         lpOleDoc
   );

   INIT_INTERFACEIMPL(
         &lpOleDoc->m_DropTarget,
         &g_OleDoc_DropTargetVtbl,
         lpOleDoc
   );
#endif  // USE_DRAGDROP

   /*
   ** OLE2NOTE: each user level document addref's the app object in
   **    order to guarentee that the app does not shut down while the
   **    doc is still open.
   */

   // OLE2NOTE: data transfer documents should not hold the app alive
   if (! fDataTransferDoc)
      OleApp_DocLockApp(lpOleApp);

#if defined( OLE_SERVER )
   /* OLE2NOTE: perform initialization specific for an OLE server */
   if (! ServerDoc_Init((LPSERVERDOC)lpOleDoc, fDataTransferDoc))
      return FALSE;
#endif
#if defined( OLE_CNTR )

   /* OLE2NOTE: perform initialization specific for an OLE container */
   if (! ContainerDoc_Init((LPCONTAINERDOC)lpOleDoc, fDataTransferDoc))
      return FALSE;
#endif

   return TRUE;
}



/* OleDoc_InitNewFile
 * ------------------
 *
 *  Initialize the document to be a new (Untitled) document.
 *  This function sets the docInitType to DOCTYPE_NEW.
 *
 *  OLE2NOTE: if this is a visible user document then generate a unique
 *  untitled name that we can use to register in the RunningObjectTable.
 *  We need a unique name so that clients can link to data in this document
 *  even when the document is in the un-saved (untitled) state. it would be
 *  ambiguous to register two documents titled "Outline1" in the ROT. we
 *  thus generate the lowest numbered document that is not already
 *  registered in the ROT.
 */
BOOL OleDoc_InitNewFile(LPOLEDOC lpOleDoc)
{
   LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC)lpOleDoc;

   static UINT uUnique = 1;

   OleDbgAssert(lpOutlineDoc->m_docInitType == DOCTYPE_UNKNOWN);

#if defined( OLE_CNTR )
   {
      LPCONTAINERDOC lpContainerDoc = (LPCONTAINERDOC)lpOleDoc;
#if defined( _DEBUG )
      OleDbgAssertSz(lpOleDoc->m_lpStg == NULL,
            "Setting to untitled with current file open"
      );
#endif

      /* Create a temp, (delete-on-release) file base storage
      **  for the untitled document.
      */
      lpOleDoc->m_lpStg = OleStdCreateRootStorage(
            NULL,
            STGM_SHARE_EXCLUSIVE
      );
      if (! lpOleDoc->m_lpStg) return FALSE;
   }
#endif

   lpOutlineDoc->m_docInitType = DOCTYPE_NEW;

   if (! lpOutlineDoc->m_fDataTransferDoc) {
      /* OLE2NOTE: choose a unique name for a Moniker so that
      **    potential clients can link to our new, untitled document.
      **    if links are established (and currently are connected),
      **    then they will be notified that we have been renamed when
      **    this document is saved to a file.
      */

      lpOleDoc->m_fLinkSourceAvail = TRUE;

      // REVIEW: should load UNTITLED string from string resource
      OleStdCreateTempFileMoniker(
            UNTITLED,
            (UINT FAR*)&uUnique,
            lpOutlineDoc->m_szFileName,
            &lpOleDoc->m_lpFileMoniker
      );

      OLEDBG_BEGIN3("OleStdRegisterAsRunning called\r\n")
      OleStdRegisterAsRunning(
            (LPUNKNOWN)&lpOleDoc->m_PersistFile,
            (LPMONIKER)lpOleDoc->m_lpFileMoniker,
            &lpOleDoc->m_dwRegROT
      );
      OLEDBG_END3

      lpOutlineDoc->m_lpszDocTitle = lpOutlineDoc->m_szFileName;
      OutlineDoc_SetTitle(lpOutlineDoc, FALSE /*fMakeUpperCase*/);
   } else {
      OLESTRCPY(lpOutlineDoc->m_szFileName, UNTITLED);
      lpOutlineDoc->m_lpszDocTitle = lpOutlineDoc->m_szFileName;
   }

   return TRUE;
}


/* OleDoc_ShowWindow
 * -----------------
 *
 *      Show the window of the document to the user.
 *      make sure app window is visible and bring the document to the top.
 *      if the document is a file-based document or a new untitled
 *      document, give the user the control over the life-time of the doc.
 */
void OleDoc_ShowWindow(LPOLEDOC lpOleDoc)
{
   LPOUTLINEAPP lpOutlineApp = (LPOUTLINEAPP)g_lpApp;
   LPOLEAPP lpOleApp = (LPOLEAPP)g_lpApp;
   LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC)lpOleDoc;
   LPLINELIST lpLL     = (LPLINELIST)&((LPOUTLINEDOC)lpOleDoc)->m_LineList;
#if defined( OLE_SERVER )
   LPSERVERDOC lpServerDoc = (LPSERVERDOC)lpOleDoc;
#endif // OLE_SERVER

   OLEDBG_BEGIN3("OleDoc_ShowWindow\r\n")

   /* OLE2NOTE: while the document is visible, we do NOT want it to be
   **    prematurely destroyed when a linking client disconnects. thus
   **    we must inform OLE to hold an external lock on our document.
   **    this arranges that OLE holds at least 1 reference to our
   **    document that will NOT be released until we release this
   **    external lock. later, when the document window is hidden, we
   **    will release this external lock.
   */
   if (! IsWindowVisible(lpOutlineDoc->m_hWndDoc))
      OleDoc_Lock(lpOleDoc, TRUE /* fLock */, 0 /* not applicable */);

#if defined( USE_DRAGDROP )
   /* OLE2NOTE: since our window is now being made visible, we will
   **    register our window as a potential drop target. when the
   **    window is hidden there is no reason to be registered as a
   **    drop target.
   */
   if (! lpOleDoc->m_fRegDragDrop) {
      OLEDBG_BEGIN2("RegisterDragDrop called\r\n")
      RegisterDragDrop(
            LineList_GetWindow(lpLL),
            (LPDROPTARGET)&lpOleDoc->m_DropTarget
      );
      OLEDBG_END2
      lpOleDoc->m_fRegDragDrop = TRUE;
   }
#endif  // USE_DRAGDROP

#if defined( USE_FRAMETOOLS )
   {
      /* OLE2NOTE: we need to enable our frame level tools
      */
      FrameTools_Enable(lpOutlineDoc->m_lpFrameTools, TRUE);
   }
#endif // USE_FRAMETOOLS

#if defined( OLE_SERVER )

   if (lpOutlineDoc->m_docInitType == DOCTYPE_EMBEDDED &&
         lpServerDoc->m_lpOleClientSite != NULL) {

      /* OLE2NOTE: we must also ask our container to show itself if
      **    it is not already visible and to scroll us into view. we
      **    must make sure to call this BEFORE showing our server's
      **    window and taking focus. we do not want our container's
      **    window to end up on top.
      */
      OLEDBG_BEGIN2("IOleClientSite::ShowObject called\r\n");
      lpServerDoc->m_lpOleClientSite->lpVtbl->ShowObject(
            lpServerDoc->m_lpOleClientSite
      );
      OLEDBG_END2

      /* OLE2NOTE: if we are an embedded object and we are not
      **    in-place active in our containers window, we must inform our
      **    embedding container that our window is opening.
      **    the container must now hatch our object.
      */

#if defined( INPLACE_SVR )
      if (! lpServerDoc->m_fInPlaceActive)
#endif
      {
         OLEDBG_BEGIN2("IOleClientSite::OnShowWindow(TRUE) called\r\n");
         lpServerDoc->m_lpOleClientSite->lpVtbl->OnShowWindow(
               lpServerDoc->m_lpOleClientSite,
               TRUE
         );
         OLEDBG_END2
      }

      /* OLE2NOTE: the life-time of our document is controlled by our
      **    client and NOT by the user. we are not an independent
      **    file-level object. we simply want to show our window here.
      **
      **    if we are not in-place active (ie. we are opening
      **    our own window), we must make sure our main app window is
      **    visible. we do not, however, want to give the user
      **    control of the App window; we do not want OleApp_ShowWindow
      **    to call OleApp_Lock on behalf of the user.
      */
      if (! IsWindowVisible(lpOutlineApp->m_hWndApp) ||
            IsIconic(lpOutlineApp->m_hWndApp)) {
#if defined( INPLACE_SVR )
         if (! ((LPSERVERDOC)lpOleDoc)->m_fInPlaceActive)
#endif
            OleApp_ShowWindow(lpOleApp, FALSE /* fGiveUserCtrl */);
         SetFocus(lpOutlineDoc->m_hWndDoc);
      }

   } else
#endif  // OLE_SERVER

   {    // DOCTYPE_NEW || DOCTYPE_FROMFILE

      // we must make sure our app window is visible
      OleApp_ShowWindow(lpOleApp, TRUE /* fGiveUserCtrl */);
   }

   // make document window visible and make sure it is not minimized
   ShowWindow(lpOutlineDoc->m_hWndDoc, SW_SHOWNORMAL);
   SetForegroundWindow(lpOutlineDoc->m_hWndDoc);

   OLEDBG_END3
}


/* OleDoc_HideWindow
 * -----------------
 *
 *      Hide the window of the document from the user.
 *      take away the control of the document by the user.
 */
void OleDoc_HideWindow(LPOLEDOC lpOleDoc, BOOL fShutdown)
{
   LPOLEAPP lpOleApp = (LPOLEAPP)g_lpApp;
   LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC)lpOleDoc;
   LPLINELIST lpLL     = (LPLINELIST)&((LPOUTLINEDOC)lpOleDoc)->m_LineList;

   if (! IsWindowVisible(lpOutlineDoc->m_hWndDoc))
      return;     // already visible

   OLEDBG_BEGIN3("OleDoc_HideWindow\r\n")

#if defined( USE_DRAGDROP )
   // The document's window is being hidden, revoke it as a DropTarget
   if (lpOleDoc->m_fRegDragDrop) {
      OLEDBG_BEGIN2("RevokeDragDrop called\r\n");
      RevokeDragDrop(LineList_GetWindow(lpLL));
      OLEDBG_END2

      lpOleDoc->m_fRegDragDrop = FALSE ;
   }
#endif  // USE_DRAGDROP

   /* OLE2NOTE: the document is now being hidden, so we must release
   **    the external lock made when the document was made visible.
   **    if this is a shutdown situation (fShutdown==TRUE), then OLE
   **    is instructed to release our document. if this is that last
   **    external lock on our document, thus enabling our document to
   **    complete its shutdown operation. If This is not a shutdown
   **    situation (eg. in-place server hiding its window when
   **    UIDeactivating or IOleObject::DoVerb(OLEVERB_HIDE) is called),
   **    then OLE is told to NOT immediately release the document.
   **    this leaves the document in an unstable state where the next
   **    Lock/Unlock sequence will shut the document down (eg. a
   **    linking client connecting and disconnecting).
   */
   if (IsWindowVisible(lpOutlineDoc->m_hWndDoc))
      OleDoc_Lock(lpOleDoc, FALSE /* fLock */, fShutdown);

   ShowWindow(((LPOUTLINEDOC)lpOleDoc)->m_hWndDoc, SW_HIDE);

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

      /* OLE2NOTE: if we are an embedded object and we are not
      **    in-place active, we must inform our
      **    embedding container that our window is hiding (closing
      **    from the user's perspective). the container must now
      **    un-hatch our object.
      */
      if (lpServerDoc->m_lpOleClientSite != NULL
#if defined( INPLACE_SVR )
         && !lpServerDoc->m_fInPlaceVisible
#endif
      ) {
         OLEDBG_BEGIN2("IOleClientSite::OnShowWindow(FALSE) called\r\n");
         lpServerDoc->m_lpOleClientSite->lpVtbl->OnShowWindow(
               lpServerDoc->m_lpOleClientSite,
               FALSE
         );
         OLEDBG_END2
      }
   }
#endif

   /* OLE2NOTE: if there are no more documents visible to the user.
   **    and the app itself is not under user control, then
   **    it has no reason to stay visible. we thus should hide the
   **    app. we can not directly destroy the app, because it may be
   **    validly being used programatically by another client
   **    application and should remain running. it should simply be
   **    hidded from the user.
   */
   OleApp_HideIfNoReasonToStayVisible(lpOleApp);
   OLEDBG_END3
}


/* OleDoc_Lock
** -----------
**    Lock/Unlock the Doc object. if the last lock is unlocked and
**    fLastUnlockReleases == TRUE, then the Doc object will shut down
**    (ie. it will recieve its final release and its refcnt will go to 0).
*/
HRESULT OleDoc_Lock(LPOLEDOC lpOleDoc, BOOL fLock, BOOL fLastUnlockReleases)
{
   HRESULT hrErr;

#if defined( _DEBUG )
   if (fLock) {
      OLEDBG_BEGIN2("CoLockObjectExternal(lpDoc,TRUE) called\r\n")
   } else {
      if (fLastUnlockReleases)
         OLEDBG_BEGIN2("CoLockObjectExternal(lpDoc,FALSE,TRUE) called\r\n")
      else
         OLEDBG_BEGIN2("CoLockObjectExternal(lpDoc,FALSE,FALSE) called\r\n")
   }
#endif  // _DEBUG

   hrErr = CoLockObjectExternal(
         (LPUNKNOWN)&lpOleDoc->m_Unknown, fLock, fLastUnlockReleases);

   OLEDBG_END2
   return hrErr;
}


/* OleDoc_AddRef
** -------------
**
**  increment the ref count of the document object.
**
**    Returns the new ref count on the object
*/
ULONG OleDoc_AddRef(LPOLEDOC lpOleDoc)
{
   ++lpOleDoc->m_cRef;

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


/* OleDoc_Release
** --------------
**
**  decrement the ref count of the document object.
**    if the ref count goes to 0, then the document is destroyed.
**
**    Returns the remaining ref count on the object
*/
ULONG OleDoc_Release (LPOLEDOC lpOleDoc)
{
   ULONG cRef;
   LPOUTLINEAPP lpOutlineApp = (LPOUTLINEAPP)g_lpApp;
   LPOLEAPP lpOleApp = (LPOLEAPP)g_lpApp;

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

   cRef = --lpOleDoc->m_cRef;

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

   OleDbgOutRefCnt4(
         "OleDoc_Release: cRef--\r\n", lpOleDoc, cRef);
#endif
   if (cRef == 0)
      OutlineDoc_Destroy((LPOUTLINEDOC)lpOleDoc);

   return cRef;
}


/* OleDoc_QueryInterface
** ---------------------
**
** Retrieve a pointer to an interface on the document object.
**
**    OLE2NOTE: this function will AddRef the ref cnt of the object.
**
**    Returns S_OK if interface is successfully retrieved.
**            E_NOINTERFACE if the interface is not supported
*/
HRESULT OleDoc_QueryInterface(
      LPOLEDOC          lpOleDoc,
      REFIID            riid,
      LPVOID FAR*       lplpvObj
)
{
   LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC)lpOleDoc;
   SCODE sc = E_NOINTERFACE;

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

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

      *lplpvObj = (LPVOID) &lpOleDoc->m_Unknown;
      OleDoc_AddRef(lpOleDoc);
      sc = S_OK;
   }
   else if(lpOutlineDoc->m_fDataTransferDoc
         && IsEqualIID(riid, &IID_IDataObject)) {
      OleDbgOut4("OleDoc_QueryInterface: IDataObject* RETURNED\r\n");

      *lplpvObj = (LPVOID) &lpOleDoc->m_DataObject;
      OleDoc_AddRef(lpOleDoc);
      sc = S_OK;
   }

   /* OLE2NOTE: if this document is a DataTransferDocument used to
   **    support a clipboard or drag/drop operation, then it should
   **    only expose IUnknown, IDataObject, and IDropSource
   **    interfaces. if the document is a normal user document, then
   **    we will also continue to consider our other interfaces.
   */
   if (lpOutlineDoc->m_fDataTransferDoc)
      goto done;

   if(IsEqualIID(riid,&IID_IPersist) || IsEqualIID(riid,&IID_IPersistFile)) {
      OleDbgOut4("OleDoc_QueryInterface: IPersistFile* RETURNED\r\n");

      *lplpvObj = (LPVOID) &lpOleDoc->m_PersistFile;
      OleDoc_AddRef(lpOleDoc);
      sc = S_OK;
   }
   else if(IsEqualIID(riid, &IID_IOleItemContainer) ||
         IsEqualIID(riid, &IID_IOleContainer) ||
         IsEqualIID(riid, &IID_IParseDisplayName) ) {
      OleDbgOut4("OleDoc_QueryInterface: IOleItemContainer* RETURNED\r\n");

      *lplpvObj = (LPVOID) &lpOleDoc->m_OleItemContainer;
      OleDoc_AddRef(lpOleDoc);
      sc = S_OK;
   }
   else if(IsEqualIID(riid, &IID_IExternalConnection)) {
      OleDbgOut4("OleDoc_QueryInterface: IExternalConnection* RETURNED\r\n");

      *lplpvObj = (LPVOID) &lpOleDoc->m_ExternalConnection;
      OleDoc_AddRef(lpOleDoc);
      sc = S_OK;
   }

#if defined( USE_DRAGDROP )
   else if(IsEqualIID(riid, &IID_IDropTarget)) {
      OleDbgOut4("OleDoc_QueryInterface: IDropTarget* RETURNED\r\n");

      *lplpvObj = (LPVOID) &lpOleDoc->m_DropTarget;
      OleDoc_AddRef(lpOleDoc);
      sc = S_OK;
   }
   else if(IsEqualIID(riid, &IID_IDropSource)) {
      OleDbgOut4("OleDoc_QueryInterface: IDropSource* RETURNED\r\n");

      *lplpvObj = (LPVOID) &lpOleDoc->m_DropSource;
      OleDoc_AddRef(lpOleDoc);
      sc = S_OK;
   }
#endif

#if defined( OLE_CNTR )
   else if (IsEqualIID(riid, &IID_IOleUILinkContainer)) {
      OleDbgOut4("OleDoc_QueryInterface: IOleUILinkContainer* RETURNED\r\n");

      *lplpvObj=(LPVOID)&((LPCONTAINERDOC)lpOleDoc)->m_OleUILinkContainer;
      OleDoc_AddRef(lpOleDoc);
      sc = S_OK;
   }
#endif

#if defined( OLE_SERVER )

   /* OLE2NOTE: if OLE server version, than also offer the server
   **    specific interfaces: IOleObject and IPersistStorage.
   */
   else if (IsEqualIID(riid, &IID_IOleObject)) {
      OleDbgOut4("OleDoc_QueryInterface: IOleObject* RETURNED\r\n");

      *lplpvObj = (LPVOID) &((LPSERVERDOC)lpOleDoc)->m_OleObject;
      OleDoc_AddRef(lpOleDoc);
      sc = S_OK;
   }
   else if(IsEqualIID(riid, &IID_IPersistStorage)) {
      OleDbgOut4("OleDoc_QueryInterface: IPersistStorage* RETURNED\r\n");

      *lplpvObj = (LPVOID) &((LPSERVERDOC)lpOleDoc)->m_PersistStorage;
      OleDoc_AddRef(lpOleDoc);
      sc = S_OK;
   }
   else if(IsEqualIID(riid, &IID_IDataObject)) {
      OleDbgOut4("OleDoc_QueryInterface: IDataObject* RETURNED\r\n");

      *lplpvObj = (LPVOID) &lpOleDoc->m_DataObject;
      OleDoc_AddRef(lpOleDoc);
      sc = S_OK;
   }

#if defined( SVR_TREATAS )
   else if(IsEqualIID(riid, &IID_IStdMarshalInfo)) {
      OleDbgOut4("OleDoc_QueryInterface: IStdMarshalInfo* RETURNED\r\n");

      *lplpvObj = (LPVOID) &((LPSERVERDOC)lpOleDoc)->m_StdMarshalInfo;
      OleDoc_AddRef(lpOleDoc);
      sc = S_OK;
   }
#endif  // SVR_TREATAS

#if defined( INPLACE_SVR )
   else if (IsEqualIID(riid, &IID_IOleWindow) ||
          IsEqualIID(riid, &IID_IOleInPlaceObject)) {
      OleDbgOut4("OleDoc_QueryInterface: IOleInPlaceObject* RETURNED\r\n");

      *lplpvObj = (LPVOID) &((LPSERVERDOC)lpOleDoc)->m_OleInPlaceObject;
      OleDoc_AddRef(lpOleDoc);
      sc = S_OK;
   }
#endif // INPLACE_SVR
#endif // OLE_SERVER

done:
   OleDbgQueryInterfaceMethod(*lplpvObj);

   return ResultFromScode(sc);
}


/* OleDoc_Close
 * ------------
 *
 *  Close the document.
 *      This functions performs the actions that are in common to all
 *      document types which derive from OleDoc (eg. ContainerDoc and
 *      ServerDoc) which are required to close a document.
 *
 *  Returns:
 *      FALSE -- user canceled the closing of the doc.
 *      TRUE -- the doc was successfully closed
 */

BOOL OleDoc_Close(LPOLEDOC lpOleDoc, DWORD dwSaveOption)
{
   LPOLEAPP lpOleApp = (LPOLEAPP)g_lpApp;
   LPOUTLINEAPP lpOutlineApp = (LPOUTLINEAPP)g_lpApp;
   LPOLEDOC lpClipboardDoc;
   LPLINELIST lpLL     = (LPLINELIST)&((LPOUTLINEDOC)lpOleDoc)->m_LineList;
   BOOL fAbortIfSaveCanceled = (dwSaveOption == OLECLOSE_PROMPTSAVE);

   if (! lpOleDoc)
      return TRUE;    // active doc's are already destroyed

   if (lpOleDoc->m_fObjIsClosing)
      return TRUE;    // Closing is already in progress

   OLEDBG_BEGIN3("OleDoc_Close\r\n")

   if (! OutlineDoc_CheckSaveChanges((LPOUTLINEDOC)lpOleDoc,&dwSaveOption)
         && fAbortIfSaveCanceled) {
      OLEDBG_END3
      return FALSE;           // cancel closing the doc
   }

   lpOleDoc->m_fObjIsClosing = TRUE;   // guard against recursive call

   /* OLE2NOTE: in order to have a stable app and doc during the
   **    process of closing, we intially AddRef the App and Doc ref
   **    cnts and later Release them. These initial AddRefs are
   **    artificial; they simply guarantee that these objects do not
   **    get destroyed until the end of this routine.
   */
   OleApp_AddRef(lpOleApp);
   OleDoc_AddRef(lpOleDoc);

#if defined( OLE_CNTR )
   {
      LPCONTAINERDOC lpContainerDoc = (LPCONTAINERDOC)lpOleDoc;

      /* OLE2NOTE: force all OLE objects to close. this forces all
      **    OLE object to transition from running to loaded. we can
      **    NOT exit if any embeddings are still running.
      **    if an object can't be closed and this close operation was
      **    started by the user, then we will abort closing our document.
      */
      if (! ContainerDoc_CloseAllOleObjects(lpContainerDoc, OLECLOSE_NOSAVE)
            && fAbortIfSaveCanceled) {
         OleDoc_Release(lpOleDoc);       // release artificial AddRef above
         OleApp_Release(lpOleApp);       // release artificial AddRef above
         lpOleDoc->m_fObjIsClosing = FALSE; // clear recursion guard

         OLEDBG_END3
         return FALSE;   // Closing is aborted
      }
   }
#endif

#if defined( INPLACE_SVR )
   /* OLE2NOTE: if the server is currently in-place active we must
   **    deactivate it now before closing
   */
   ServerDoc_DoInPlaceDeactivate((LPSERVERDOC)lpOleDoc);
#endif

   /* OLE2NOTE: if this document is the source of data for the
   **    clipboard, then flush the clipboard. it is important to flush
   **    the clipboard BEFORE calling sending any notifications to
   **    clients (eg. IOleClientSite::OnShowWindow(FALSE)) which could
   **    give them a chance to run and try to get our clipboard data
   **    object that we want to destroy. (eg. our app tries to
   **    update the paste button of the toolbar when
   **    WM_ACTIVATEAPP is received.)
   */
   lpClipboardDoc = (LPOLEDOC)lpOutlineApp->m_lpClipboardDoc;
   if (lpClipboardDoc &&
      lpClipboardDoc->m_lpSrcDocOfCopy == lpOleDoc) {
      OleApp_FlushClipboard(lpOleApp);
   }

   /* OLE2NOTE: Revoke the object from the Running Object Table. it is
   **    best if the object is revoke prior to calling
   **    COLockObjectExternal(FALSE,TRUE) which is called when the
   **    document window is hidden from the user.
   */
   OLEDBG_BEGIN3("OleStdRevokeAsRunning called\r\n")
   OleStdRevokeAsRunning(&lpOleDoc->m_dwRegROT);
   OLEDBG_END3

   /* OLE2NOTE: if the user is in control of the document, the user
   **    accounts for one refcnt on the document. Closing the
   **    document is achieved by releasing the object on behalf of
   **    the user. if the document is not referenced by any other
   **    clients, then the document will also be destroyed. if it
   **    is referenced by other clients, then it will remain until
   **    they release it. it is important to hide the window and call
   **    IOleClientSite::OnShowWindow(FALSE) BEFORE sending OnClose
   **    notification.
   */
   OleDoc_HideWindow(lpOleDoc, TRUE);

#if defined( OLE_SERVER )
   {
      LPSERVERDOC lpServerDoc = (LPSERVERDOC)lpOleDoc;
      LPSERVERNAMETABLE lpServerNameTable =
         (LPSERVERNAMETABLE)((LPOUTLINEDOC)lpOleDoc)->m_lpNameTable;

      /* OLE2NOTE: force all pseudo objects to close. this informs all
      **    linking clients of pseudo objects to release their PseudoObj.
      */
      ServerNameTable_CloseAllPseudoObjs(lpServerNameTable);

      /* OLE2NOTE: send last OnDataChange notification to clients
      **    that have registered for data notifications when object
      **    stops running (ADVF_DATAONSTOP), if the data in our
      **    object has ever changed. it is best to only send this
      **    notification if necessary.
      */
      if (lpServerDoc->m_lpDataAdviseHldr) {
         if (lpServerDoc->m_fSendDataOnStop) {
            ServerDoc_SendAdvise(
                  (LPSERVERDOC)lpOleDoc,
                  OLE_ONDATACHANGE,
                  NULL,   /* lpmkDoc -- not relevant here */
                  ADVF_DATAONSTOP
            );
         }
         /* OLE2NOTE: we just sent the last data notification that we
         **    need to send; release our DataAdviseHolder. we SHOULD be
         **    the only one using it.
         */

         OleStdVerifyRelease(
               (LPUNKNOWN)lpServerDoc->m_lpDataAdviseHldr,
               OLESTR("DataAdviseHldr not released properly")
         );
         lpServerDoc->m_lpDataAdviseHldr = NULL;
      }

      // OLE2NOTE: inform all of our linking clients that we are closing.


      if (lpServerDoc->m_lpOleAdviseHldr) {
         ServerDoc_SendAdvise(
               (LPSERVERDOC)lpOleDoc,
               OLE_ONCLOSE,
               NULL,   /* lpmkDoc -- not relevant here */
               0       /* advf -- not relevant here */
         );

         /* OLE2NOTE: OnClose is the last notification that we need to
         **    send; release our OleAdviseHolder. we SHOULD be the only
         **    one using it. this will make our destructor realize that
         **    OnClose notification has already been sent.
         */
         OleStdVerifyRelease(
               (LPUNKNOWN)lpServerDoc->m_lpOleAdviseHldr,
               OLESTR("OleAdviseHldr not released properly")
         );
         lpServerDoc->m_lpOleAdviseHldr = NULL;
      }

      /* release our Container's ClientSite. */
      if(lpServerDoc->m_lpOleClientSite) {
         OleStdRelease((LPUNKNOWN)lpServerDoc->m_lpOleClientSite);
         lpServerDoc->m_lpOleClientSite = NULL;
      }
   }
#endif

   if (lpOleDoc->m_lpLLStm) {
      /* release our LineList stream. */
      OleStdRelease((LPUNKNOWN)lpOleDoc->m_lpLLStm);
      lpOleDoc->m_lpLLStm = NULL;
   }

   if (lpOleDoc->m_lpNTStm) {
      /* release our NameTable stream. */
      OleStdRelease((LPUNKNOWN)lpOleDoc->m_lpNTStm);
      lpOleDoc->m_lpNTStm = NULL;
   }

   if (lpOleDoc->m_lpStg) {
      /* release our doc storage. */
      OleStdRelease((LPUNKNOWN)lpOleDoc->m_lpStg);
      lpOleDoc->m_lpStg = NULL;
   }

   if (lpOleDoc->m_lpFileMoniker) {
      OleStdRelease((LPUNKNOWN)lpOleDoc->m_lpFileMoniker);
      lpOleDoc->m_lpFileMoniker = NULL;
   }

   /* OLE2NOTE: this call forces all external connections to our
   **    object to close down and therefore guarantees that we receive
   **    all releases associated with those external connections.
   */
   OLEDBG_BEGIN2("CoDisconnectObject(lpDoc) called\r\n")
   CoDisconnectObject((LPUNKNOWN)&lpOleDoc->m_Unknown, 0);
   OLEDBG_END2

   OleDoc_Release(lpOleDoc);       // release artificial AddRef above
   OleApp_Release(lpOleApp);       // release artificial AddRef above

   OLEDBG_END3
   return TRUE;
}


/* OleDoc_Destroy
 * --------------
 *
 *  Free all OLE related resources that had been allocated for a document.
 */
void OleDoc_Destroy(LPOLEDOC lpOleDoc)
{
   LPOUTLINEAPP lpOutlineApp = (LPOUTLINEAPP)g_lpApp;
   LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC)lpOleDoc;

   if (lpOleDoc->m_fObjIsDestroying)
      return;     // Doc destruction is already in progress

   lpOleDoc->m_fObjIsDestroying = TRUE;    // guard against recursive call

#if defined( OLE_SERVER )

   /* OLE2NOTE: it is ALWAYS necessary to make sure that the work we
   **    do in our OleDoc_Close function is performed before we
   **    destroy our document object. this includes revoking from the
   **    Running Object Table (ROT), sending OnClose notification,
   **    revoking from Drag/Drop, closing all pseudo objects, etc.
   **    There are some tricky scenarios involving linking and
   **    when IOleObject::Close is called versus when we get our
   **    final release causing us to call our OleDoc_Destroy
   **    (destructor) function.
   **
   **    SCENARIO 1 -- closing from server (File.Exit or File.Close)
   **                  OleDoc_Close function is called directly by the
   **                  server in response to the menu command
   **                  (WM_COMMAND processing).
   **
   **    SCENARIO 2 -- closed by embedding container
   **                  our embedding container calls IOleObject::Close
   **                  directly.
   **
   **    SCENARIO 3 -- silent-update final release
   **                  THIS IS THE TRICKY ONE!!!
   **                  in the case that our object is launched because
   **                  a linking client calls IOleObject::Update on
   **                  its link, then our object will be run
   **                  invisibly, typically GetData will be called,
   **                  and then the connection from the linking client
   **                  will be released. the release of this last
   **                  linking connection should cause our object to
   **                  shut down.
   **                  there are 2 strategies to deal with this scenario:
   **
   **                  STRATEGY 1 -- implement IExternalConnection.
   **                  IExternalConnection::AddConnection will be
   **                  called (by the StubManager) every time that an
   **                  external (linking) connection is created or
   **                  CoLockObjectExternal is called. the object
   **                  should maintain a count of strong connections
   **                  (m_dwStrongExtConn). IExternalConnection::
   **                  ReleaseConnection will be called when these
   **                  connections are released. when the
   **                  m_dwStrongExtConn transistions to 0, the object
   **                  should call its IOleObject::Close function.
   **                  this assumes that CoLockObjectExternal is used
   **                  to manage locks by the object itself (eg. when
   **                  the object is visible to the user--fUserCtrl,
   **                  and when PseudoObjects are created, etc.)
   **                  this is the strategy implemented by SVROUTL.
   **
   **                  STRATEGY 2 -- guard both the destructor
   **                  function and the Close function. if the
   **                  destructor is called directly without Close
   **                  first being called, then call Close before
   **                  proceeding with the destruction code.
   **                  previously SVROUTL was organized in this
   **                  manner. that old code is conditionaly compiled
   **                  away with "#ifdef OBSOLETE" below. this
   **                  method has the disadvantage that external
   **                  remoting is no longer possible by the time the
   **                  Close is called making it impossible for
   **                  the object to ask its container to save the
   **                  object if the object is dirty. this can result
   **                  in data loss. thus STRATEGY 1 is safer.
   **                  consider the scenario where an in-place
   **                  container UIDeactivates an object but does NOT
   **                  keep the object locked running (this is
   **                  required--see CntrLine_IPSite_OnInPlaceActivate
   **                  in cntrline.c), then, if a linking client binds
   **                  and unbinds from the object, the object will be
   **                  destroyed and will NOT have an opportunity to
   **                  be saved. by implementing IExternalConnection,
   **                  a server can insulate itself from a poorly
   **                  written container.
   */
#if defined( _DEBUG )
   OleDbgAssertSz(
         (lpOutlineDoc->m_fDataTransferDoc || lpOleDoc->m_fObjIsClosing),
         "Destroy called without Close being called\r\n"
   );
#endif  // _DEBUG
#if defined( OBSOLETE )
   /* OLE2NOTE: if the document destructor is called directly because
   **    the object's refcnt went to 0 (ie. without OleDoc_Close first
   **    being called), then we need to make sure that the document is
   **    properly closed before destroying the object. this scenario
   **    could arise during a silent-update of a link. calling
   **    OleDoc_Close here guarantees that the clipboard will be
   **    properly flushed, the doc's moniker will be properly revoked,
   **    the document will be saved if necessary, etc.
   */
   if (!lpOutlineDoc->m_fDataTransferDoc && !lpOleDoc->m_fObjIsClosing)
      OleDoc_Close(lpOleDoc, OLECLOSE_NOSAVE);
#endif

   {
      LPSERVERDOC lpServerDoc = (LPSERVERDOC)lpOleDoc;
      /* OLE2NOTE: perform processing specific for an OLE server */

#if defined( SVR_TREATAS )
      if (lpServerDoc->m_lpszTreatAsType) {
         OleStdFreeString(lpServerDoc->m_lpszTreatAsType, NULL);
         lpServerDoc->m_lpszTreatAsType = NULL;
      }
#endif  // SVR_TREATAS

#if defined( INPLACE_SVR )
      if (IsWindow(lpServerDoc->m_hWndHatch))
         DestroyWindow(lpServerDoc->m_hWndHatch);
#endif  // INPLACE_SVR
   }
#endif  // OLE_SERVER

   if (lpOleDoc->m_lpLLStm) {
      /* release our LineList stream. */
      OleStdRelease((LPUNKNOWN)lpOleDoc->m_lpLLStm);
      lpOleDoc->m_lpLLStm = NULL;
   }

   if (lpOleDoc->m_lpNTStm) {
      /* release our NameTable stream. */
      OleStdRelease((LPUNKNOWN)lpOleDoc->m_lpNTStm);
      lpOleDoc->m_lpNTStm = NULL;
   }

   if (lpOleDoc->m_lpStg) {
      /* release our doc storage. */
      OleStdRelease((LPUNKNOWN)lpOleDoc->m_lpStg);
      lpOleDoc->m_lpStg = NULL;
   }

   if (lpOleDoc->m_lpFileMoniker) {
      OleStdRelease((LPUNKNOWN)lpOleDoc->m_lpFileMoniker);
      lpOleDoc->m_lpFileMoniker = NULL;
   }

   /*****************************************************************
   ** OLE2NOTE: each document addref's the app object in order to  **
   **    guarentee that the app does not shut down while the doc   **
   **    is still open. since this doc is now destroyed, we will   **
   **    release this refcnt now. if there are now more open       **
   **    documents AND the app is not under the control of the     **
   **    user (ie. launched by OLE) then the app will revoke its   **
   **    ClassFactory. if there are no more references to the      **
   **    ClassFactory after it is revoked, then the app will shut  **
   **    down. this whole procedure is triggered by calling        **
   **    OutlineApp_DocUnlockApp.                                  **
   *****************************************************************/

   OutlineApp_DocUnlockApp(lpOutlineApp, lpOutlineDoc);
}


/* OleDoc_SetUpdateEditMenuFlag
 * ----------------------------
 *
 *  Purpose:
 *      Set/clear the UpdateEditMenuFlag in OleDoc.
 *
 *  Parameters:
 *      fUpdate     new value of the flag
 *
 *  Returns:
 */
void OleDoc_SetUpdateEditMenuFlag(LPOLEDOC lpOleDoc, BOOL fUpdate)
{
   if (!lpOleDoc)
      return;

   lpOleDoc->m_fUpdateEditMenu = fUpdate;
}


/* OleDoc_GetUpdateEditMenuFlag
 * ----------------------------
 *
 *  Purpose:
 *      Get the value of the UpdateEditMenuFlag in OleDoc
 *
 *  Parameters:
 *
 *  Returns:
 *      value of the flag
 */
BOOL OleDoc_GetUpdateEditMenuFlag(LPOLEDOC lpOleDoc)
{
   if (!lpOleDoc)
      return FALSE;

   return lpOleDoc->m_fUpdateEditMenu;
}



/*************************************************************************
** OleDoc::IUnknown interface implementation
*************************************************************************/

STDMETHODIMP OleDoc_Unk_QueryInterface(
      LPUNKNOWN           lpThis,
      REFIID              riid,
      LPVOID FAR*         lplpvObj
)
{
   LPOLEDOC lpOleDoc = ((struct CDocUnknownImpl FAR*)lpThis)->lpOleDoc;

   return OleDoc_QueryInterface(lpOleDoc, riid, lplpvObj);
}


STDMETHODIMP_(ULONG) OleDoc_Unk_AddRef(LPUNKNOWN lpThis)
{
   LPOLEDOC lpOleDoc = ((struct CDocUnknownImpl FAR*)lpThis)->lpOleDoc;

   OleDbgAddRefMethod(lpThis, "IUnknown");

   return OleDoc_AddRef(lpOleDoc);
}


STDMETHODIMP_(ULONG) OleDoc_Unk_Release (LPUNKNOWN lpThis)
{
   LPOLEDOC lpOleDoc = ((struct CDocUnknownImpl FAR*)lpThis)->lpOleDoc;

   OleDbgReleaseMethod(lpThis, "IUnknown");

   return OleDoc_Release(lpOleDoc);
}
