//**********************************************************************
// File name: DOC.CPP
//
//      Implementation file for CSimpleDoc.
//
// Functions:
//
//      See DOC.H for Class Definition
//
// Copyright (c) 1992 - 1993 Microsoft Corporation. All rights reserved.
//**********************************************************************

#include "pre.h"
#include "iocs.h"
#include "ias.h"
#include "app.h"
#include "site.h"
#include "doc.h"
#include "idt.h"
#include "dxferobj.h"

//**********************************************************************
//
// CSimpleDoc::Create
//
// Purpose:
//
//      Creation for the CSimpleDoc Class
//
// Parameters:
//
//      CSimpleApp FAR * lpApp  -   Pointer to the CSimpleApp Class
//
//      LPRECT lpRect           -   Client area rect of "frame" window
//
//      HWND hWnd               -   Window Handle of "frame" window
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                    Location
//
//      StgCreateDocfile            OLE API
//      RegisterDragDrop            OLE API
//      CoLockObjectExternal        OLE API
//      CreateWindow                Windows API
//      ShowWindow                  Windows API
//      UpdateWindow                Windows API
//      EnableMenuItem              Windows API
//
// Comments:
//
//      This routine was added so that failure could be returned
//      from object creation.
//
//********************************************************************

CSimpleDoc FAR * CSimpleDoc::Create(CSimpleApp FAR *lpApp, LPRECT lpRect,
                                    HWND hWnd)
{
    CSimpleDoc FAR * lpTemp = new CSimpleDoc(lpApp, hWnd);

    if (!lpTemp)
    {
        TestDebugOut("Memory allocation error\n");
        return NULL;
    }

    // create storage for the doc.
    HRESULT hErr = StgCreateDocfile (
        NULL,       // generate temp name
        STGM_READWRITE | STGM_TRANSACTED | STGM_SHARE_EXCLUSIVE,
        0, &lpTemp->m_lpStorage);

    if (hErr != NOERROR)
        goto error;

    // create the document Window
    lpTemp->m_hDocWnd = CreateWindow(
            TEXT("SimpDndDocWClass"),
            NULL,
            WS_CHILD | WS_CLIPCHILDREN,
            lpRect->left,
            lpRect->top,
            lpRect->right,
            lpRect->bottom,
            hWnd,
            NULL,
            lpApp->m_hInst,
            NULL);

    if (!lpTemp->m_hDocWnd)
        goto error;

    ShowWindow(lpTemp->m_hDocWnd, SW_SHOWNORMAL);  // Show the window
    UpdateWindow(lpTemp->m_hDocWnd);               // Sends WM_PAINT message

#ifdef NOTREADY
    // Ensable InsertObject menu choice
    EnableMenuItem( lpApp->m_hEditMenu, 0, MF_BYPOSITION | MF_ENABLED);
#else
    // Ensable InsertObject menu choice
    EnableMenuItem( lpApp->m_hEditMenu, 1, MF_BYPOSITION | MF_ENABLED);
    // Disable Copy menu choice
    EnableMenuItem( lpApp->m_hEditMenu, 0, MF_BYPOSITION | MF_DISABLED |
                                           MF_GRAYED);
#endif // NOTREADY

    HRESULT  hRes;

    // It is *REQUIRED* to hold a strong LOCK on the object that is
    // registered as drop target. this call will result in at least one
    // ref count held on our document. later in CSimpleDoc::Close we will
    // unlock this lock which will make our document's ref count go to 0.
    // when the document's ref count goes to 0, it will be deleted.
    if ( (hRes=CoLockObjectExternal (&lpTemp->m_DropTarget, TRUE, 0))
         != ResultFromScode(S_OK) )
    {
       /* CoLockObjectExternal should never fail. If it fails, we don't want
        * to carry on since we don't have a guaranteed object lock.
        */
       goto error;
    }

    // Register our window as a DropTarget
    if (((hRes=RegisterDragDrop(lpTemp->m_hDocWnd, &lpTemp->m_DropTarget))
          !=ResultFromScode(S_OK))
          && (hRes != ResultFromScode(DRAGDROP_E_ALREADYREGISTERED)))
    {
       lpTemp->m_fRegDragDrop = FALSE;
    }
    else
    {
       lpTemp->m_fRegDragDrop = TRUE;
    }

    return (lpTemp);

error:
    TestDebugOut("Fail in CSimpleDoc::Create\n");
    delete (lpTemp);
    return NULL;

}

//**********************************************************************
//
// CSimpleDoc::Close
//
// Purpose:
//
//      Close CSimpleDoc object.
//      when the document's reference count goes to 0, the document
//      will be destroyed.
//
// Parameters:
//
//      None
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                    Location
//
//      RevokeDragDrop              OLE API
//      CoLockObjectExternal        OLE API
//      OleFlushClipboard           OLE API
//      ShowWindow                  Windows API
//      CSimpleSite::CloseOleObject SITE.CPP
//
//
//********************************************************************

void CSimpleDoc::Close(void)
{
    TestDebugOut("In CSimpleDoc::Close\r\n");

    HRESULT hRes;

    ShowWindow(m_hDocWnd, SW_HIDE);  // Hide the window

    // Remove our data transfer object from clipboard if it is there.
    // this will leave HGLOBAL based data behind on the clipboard
    // including OLE 1.0 compatibility formats.

    if (OleFlushClipboard() != ResultFromScode(S_OK))
    {
       TestDebugOut("Fail in OleFlushClipBoard\n");
    }

    // Revoke our window as a DropTarget
    if (m_fRegDragDrop)
    {
        if (((hRes=RevokeDragDrop(m_hDocWnd)) != ResultFromScode(S_OK)) &&
             (hRes!=ResultFromScode(DRAGDROP_E_NOTREGISTERED)))
        {
           /* if we fail in revoking the drag-drop, we will probably be
            * having memory leakage.
            */
           TestDebugOut("Fail in RevokeDragDrop\n");
        }
        else
        {
           m_fRegDragDrop = FALSE;
        }
    }

    // Close the OLE object in our document
    if (m_lpSite)
        m_lpSite->CloseOleObject();

    // Unlock the lock added in CSimpleDoc::Create. this will make
    // the document's ref count go to 0, and the document will be deleted.
    if ((hRes=CoLockObjectExternal (&m_DropTarget, FALSE, TRUE))
        !=ResultFromScode(S_OK))
    {
        /* if CoLockObjectExternal fails, this means that we cannot release
         * the reference count to our destinated object. This will cause
         * memory leakage.
         */
        TestDebugOut("Fail in CoLockObjectExternal\n");
    }
}

//**********************************************************************
//
// CSimpleDoc::CSimpleDoc
//
// Purpose:
//
//      Constructor for the CSimpleDoc Class
//
// Parameters:
//
//      CSimpleApp FAR * lpApp  -   Pointer to the CSimpleApp Class
//
//      HWND hWnd               -   Window Handle of "frame" window
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                    Location
//
//      TestDebugOut           Windows API
//      GetMenu                     Windows API
//      GetSubMenu                  Windows API
//
//
//********************************************************************
#pragma warning(disable : 4355)  // turn off this warning.  This warning
                                 // tells us that we are passing this in
                                 // an initializer, before "this" is through
                                 // initializing.  This is ok, because
                                 // we just store the ptr in the other
                                 // constructor

CSimpleDoc::CSimpleDoc(CSimpleApp FAR * lpApp,HWND hWnd)
        : m_DropTarget(this), m_DropSource(this)
#pragma warning (default : 4355)  // Turn the warning back on
{
    TestDebugOut("In CSimpleDoc's Constructor\r\n");
    m_lpApp = lpApp;
    m_lpSite = NULL;
    m_nCount = 0;
    // set up menu handles
    lpApp->m_hMainMenu = GetMenu(hWnd);
    lpApp->m_hFileMenu = GetSubMenu(lpApp->m_hMainMenu, 0);
    lpApp->m_hEditMenu = GetSubMenu(lpApp->m_hMainMenu, 1);
    lpApp->m_hHelpMenu = GetSubMenu(lpApp->m_hMainMenu, 2);
    lpApp->m_hCascadeMenu = NULL;
    m_fModifiedMenu = FALSE;

    // drag/drop related stuff
    m_fRegDragDrop = FALSE;       // is doc registered as drop target?
    m_fLocalDrag = FALSE;         // is doc source of the drag
    m_fLocalDrop = FALSE;         // was doc target of the drop
    m_fCanDropCopy = FALSE;       // is Drag/Drop copy/move possible?
    m_fCanDropLink = FALSE;       // is Drag/Drop link possible?
    m_fDragLeave = FALSE;         // has drag left
    m_fPendingDrag = FALSE;       // LButtonDown--possible drag pending
    m_ptButDown.x = m_ptButDown.y = 0; // LButtonDown coordinates

}

//**********************************************************************
//
// CSimpleDoc::~CSimpleDoc
//
// Purpose:
//
//      Destructor for CSimpleDoc
//
// Parameters:
//
//      None
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                     Location
//
//      TestDebugOut            Windows API
//      GetMenuItemCount             Windows API
//      RemoveMenu                   Windows API
//      DestroyMenu                  Windows API
//      DestroyWindow                Windows API
//      CSimpleSite::Release         SITE.CPP
//      CSimpleSite::UnloadOleObject SITE.CPP
//      IStorage::Release            OLE API
//
//
//********************************************************************

CSimpleDoc::~CSimpleDoc()
{
    TestDebugOut("In CSimpleDoc's Destructor\r\n");

    // Release all pointers we hold to the OLE object, also release
    // the ref count added in CSimpleSite::Create. this will make
    // the Site's ref count go to 0, and the Site will be deleted.
    if (m_lpSite)
    {
        m_lpSite->UnloadOleObject();
        m_lpSite->Release();
        m_lpSite = NULL;
    }

    // Release the Storage
    if (m_lpStorage)
    {
        m_lpStorage->Release();
        m_lpStorage = NULL;
    }

    // if the edit menu was modified, remove the menu item and
    // destroy the popup if it exists
    if (m_fModifiedMenu)
    {
        int nCount = GetMenuItemCount(m_lpApp->m_hEditMenu);
        RemoveMenu(m_lpApp->m_hEditMenu, nCount-1, MF_BYPOSITION);
        if (m_lpApp->m_hCascadeMenu)
            DestroyMenu(m_lpApp->m_hCascadeMenu);
    }

    DestroyWindow(m_hDocWnd);
}


//**********************************************************************
//
// CSimpleDoc::QueryInterface
//
// Purpose:
//
//      Used for interface negotiation at the Document level.
//
// Parameters:
//
//      REFIID riid         -   ID of interface to be returned
//      LPVOID FAR* ppvObj  -   Location to return the interface
//
// Return Value:
//
//      S_OK                -   Interface supported
//      E_NOINTERFACE       -   Interface NOT supported
//
// Function Calls:
//      Function                    Location
//
//      TestDebugOut           Windows API
//      ResultFromScode             OLE API
//
//
//********************************************************************

STDMETHODIMP CSimpleDoc::QueryInterface(REFIID riid, LPVOID FAR* ppvObj)
{
    TestDebugOut("In CSimpleDoc::QueryInterface\r\n");

    *ppvObj = NULL;     // must set out pointer parameters to NULL

    // looking for IUnknown
    if (IsEqualIID( riid, IID_IUnknown))
    {
        AddRef();
        *ppvObj = this;
        return ResultFromScode(S_OK);
    }

    // looking for IDropTarget
    if (IsEqualIID( riid, IID_IDropTarget))
    {
        m_DropTarget.AddRef();
        *ppvObj=&m_DropTarget;
        return ResultFromScode(S_OK);
    }

    // looking for IDropSource
    if (IsEqualIID( riid, IID_IDropSource))
    {
        m_DropSource.AddRef();
        *ppvObj=&m_DropSource;
        return ResultFromScode(S_OK);
    }

    // Not a supported interface
    return ResultFromScode(E_NOINTERFACE);
}

//**********************************************************************
//
// CSimpleDoc::AddRef
//
// Purpose:
//
//      Increments the document reference count
//
// Parameters:
//
//      None
//
// Return Value:
//
//      UINT    -   The new reference count on the document object
//
// Function Calls:
//      Function                    Location
//
//      TestDebugOut           Windows API
//      CSimpleApp::AddRef          APP.CPP
//
//
//********************************************************************

STDMETHODIMP_(ULONG) CSimpleDoc::AddRef()
{
    TestDebugOut("In CSimpleDoc::AddRef\r\n");
    return ++m_nCount;
}

//**********************************************************************
//
// CSimpleDoc::Release
//
// Purpose:
//
//      Decrements the document reference count
//
// Parameters:
//
//      None
//
// Return Value:
//
//      UINT    -   The new reference count on the document
//
// Function Calls:
//      Function                    Location
//
//      TestDebugOut           Windows API
//
//
//********************************************************************

STDMETHODIMP_(ULONG) CSimpleDoc::Release()
{
    TestDebugOut("In CSimpleDoc::Release\r\n");

    if (--m_nCount == 0)
    {
        delete this;
        return 0;
    }
    return m_nCount;
}

//**********************************************************************
//
// CSimpleDoc::InsertObject
//
// Purpose:
//
//      Inserts a new object to this document
//
// Parameters:
//
//      None
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                    Location
//
//      CSimpleSite::Create         SITE.CPP
//      CSimpleSite::InitObject     SITE.CPP
//      CSimpleSite::Release        SITE.CPP
//      CSimpleSite::Revert         SITE.CPP
//      memset                      C Runtime
//      OleUIInsertObject           OLE2UI function
//      CSimpleDoc::DisableInsertObject DOC.CPP
//
// Comments:
//
//      This implementation only allows one object to be inserted
//      into a document.  Once the object has been inserted, then
//      the Insert Object menu choice is greyed out, to prevent
//      the user from inserting another.
//
//********************************************************************

void CSimpleDoc::InsertObject()
{
    OLEUIINSERTOBJECT io;
    UINT iret;
    TCHAR szFile[OLEUI_CCHPATHMAX];

    HRESULT hRes;

    m_lpSite = CSimpleSite::Create(this);

    if (!m_lpSite)
    {
       /* memory allocation problem. cannot continue.
        */
       TestDebugOut("Memory allocation error\n");
       return;
    }

    // clear the structure
    _fmemset(&io, 0, sizeof(OLEUIINSERTOBJECT));

    // fill the structure
    io.cbStruct = sizeof(OLEUIINSERTOBJECT);
    io.dwFlags = IOF_SELECTCREATENEW      | IOF_DISABLELINK     |
                 IOF_DISABLEDISPLAYASICON | IOF_CREATENEWOBJECT |
                 IOF_CREATEFILEOBJECT;
    io.hWndOwner = m_hDocWnd;
    io.lpszCaption = (LPTSTR)TEXT("Insert Object");
    io.iid = IID_IOleObject;
    io.oleRender = OLERENDER_DRAW;
    io.lpIOleClientSite = &m_lpSite->m_OleClientSite;
    io.lpIStorage = m_lpSite->m_lpObjStorage;
    io.ppvObj = (LPVOID FAR *)&m_lpSite->m_lpOleObject;
    io.lpszFile = szFile;
    io.cchFile = sizeof(szFile)/sizeof(TCHAR);
                                      // cchFile is the number of characters
    _fmemset((LPTSTR)szFile, 0, sizeof(szFile));

    // call OUTLUI to do all the hard work
    iret = OleUIInsertObject(&io);

    if (iret == OLEUI_OK)
    {
        m_lpSite->InitObject((BOOL)(io.dwFlags & IOF_SELECTCREATENEW));
        // disable Insert Object menu item
        DisableInsertObject();
    }
    else
    {
        m_lpSite->Release();
        m_lpSite = NULL;
        if (((hRes=m_lpStorage->Revert()) != ResultFromScode(S_OK)) &&
             (hRes!=ResultFromScode(STG_E_REVERTED)))
        {
           TestDebugOut("Fail in IStorage::Revert\n");
        }
    }
}

//**********************************************************************
//
// CSimpleDoc::lResizeDoc
//
// Purpose:
//
//      Resizes the document
//
// Parameters:
//
//      LPRECT lpRect   -   The size of the client are of the "frame"
//                          Window.
//
// Return Value:
//
//      NULL
//
// Function Calls:
//      Function                                Location
//
//      MoveWindow                              Windows API
//
//
//********************************************************************

long CSimpleDoc::lResizeDoc(LPRECT lpRect)
{
    MoveWindow(
            m_hDocWnd,
            lpRect->left, lpRect->top,
            lpRect->right, lpRect->bottom, TRUE);

    return NULL;
}

//**********************************************************************
//
// CSimpleDoc::lAddVerbs
//
// Purpose:
//
//      Adds the objects verbs to the edit menu.
//
// Parameters:
//
//      None
//
// Return Value:
//
//      NULL
//
// Function Calls:
//      Function                    Location
//
//      GetMenuItemCount            Windows API
//      OleUIAddVerbMenu            OLE2UI function
//
//
//********************************************************************

long CSimpleDoc::lAddVerbs(void)
{
    // m_fModifiedMenu is TRUE if the menu has already been modified
    // once.  Since we only support one obect every time the application
    // is run, then once the menu is modified, it doesn't have
    // to be done again.
    if (m_lpSite && !m_fModifiedMenu)
    {
        int nCount = GetMenuItemCount(m_lpApp->m_hEditMenu);

        if (!OleUIAddVerbMenu ( m_lpSite->m_lpOleObject,
                           NULL,
                           m_lpApp->m_hEditMenu,
                           nCount + 1,
                           IDM_VERB0,
                           0,           // no maximum verb IDM enforced
                           FALSE,
                           1,
                           &m_lpApp->m_hCascadeMenu) )
        {
            TestDebugOut("Fail in OleUIAddVerbMenu\n");
        }

        m_fModifiedMenu = TRUE;
    }
    return (NULL);
}

//**********************************************************************
//
// CSimpleDoc::PaintDoc
//
// Purpose:
//
//      Paints the Document
//
// Parameters:
//
//      HDC hDC -   hDC of the document Window
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                    Location
//
//      CSimpleSite::PaintObj       SITE.CPP
//
//
//********************************************************************

void CSimpleDoc::PaintDoc (HDC hDC)
{
    // if we supported multiple objects, then we would enumerate
    // the objects and call paint on each of them from here.

    if (m_lpSite)
        m_lpSite->PaintObj(hDC);

}

//**********************************************************************
//
// CSimpleDoc::DisableInsertObject
//
// Purpose:
//
//      Disable the ability to insert a new object in this document.
//
// Parameters:
//
//      None
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                    Location
//
//      RevokeDragDrop              OLE API
//      EnableMenuItem              Windows API
//
// Comments:
//
//      This implementation only allows one object to be inserted
//      into a document.  Once the object has been inserted, then
//      the Insert Object menu choice is greyed out, to prevent
//      the user from inserting another. Also we revoke ourself as
//      a potential drop target.
//
//********************************************************************

void CSimpleDoc::DisableInsertObject(void)
{
#ifdef NOTREADY
    // Disable InsertObject menu choice
    EnableMenuItem( m_lpApp->m_hEditMenu, 0, MF_BYPOSITION | MF_DISABLED |
                                             MF_GRAYED);
#else
    // Disable InsertObject menu choice
    EnableMenuItem( m_lpApp->m_hEditMenu, 1, MF_BYPOSITION | MF_DISABLED |
                                             MF_GRAYED);
    // Enable Copy menu choice
    EnableMenuItem( m_lpApp->m_hEditMenu, 0, MF_BYPOSITION | MF_ENABLED);
#endif // NOTREADY

    // We no longer accept dropping of objects
    if (m_fRegDragDrop)
    {
        HRESULT hRes;
        if (((hRes=RevokeDragDrop(m_hDocWnd))!=ResultFromScode(S_OK)) &&
             (hRes!=ResultFromScode(DRAGDROP_E_NOTREGISTERED)))
        {
           /* if we fail in revoking the drag-drop, we will probably be
            * having memory leakage.
            */
           TestDebugOut("Fail in RevokeDragDrop\n");
        }
        else
        {
           m_fRegDragDrop = FALSE;
        }
    }
}

//**********************************************************************
//
// CSimpleDoc::CopyObjectToClip
//
// Purpose:
//
//      Copy the embedded OLE object to the clipboard
//
// Parameters:
//
//      None
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                    Location
//
//      CDataXferObj::Create        DXFEROBJ.CPP
//      CDataXferObj::Release       DXFEROBJ.CPP
//      CDataXferObj::QueryInterface DXFEROBJ.CPP
//      OleSetClipboard             OLE API
//
// Comments:
//
//      This implementation only allows one object to be inserted
//      into a document.  Once the object has been inserted, then
//      the Copy menu choice is enabled.
//
//********************************************************************

void CSimpleDoc::CopyObjectToClip(void)
{
    LPDATAOBJECT lpDataObj;

    // Create a data transfer object by cloning the existing OLE object
    CDataXferObj FAR* pDataXferObj = CDataXferObj::Create(m_lpSite,NULL);
    if (! pDataXferObj)
    {
        /* memory allocation error !
         */
        MessageBox(NULL, TEXT("Out-of-memory"), TEXT("SimpDnD"),
                   MB_SYSTEMMODAL | MB_ICONHAND);
        return;
    }
    // initially obj is created with 0 refcnt. this QI will make it go to 1.
    pDataXferObj->QueryInterface(IID_IDataObject, (LPVOID FAR*)&lpDataObj);

    // put out data transfer object on the clipboard. this API will AddRef.
    if (OleSetClipboard(lpDataObj) != ResultFromScode(S_OK))
    {
       TestDebugOut("Fail in OleSetClipboard\n");
       lpDataObj->Release();
    }

    // Give ownership of data transfer object to clipboard
    pDataXferObj->Release();
}