/*
  OLE SERVER DEMO
  Doc.c

  This file contains document methods and various document-related support
  functions.

  (c) Copyright Microsoft Corp. 1990 - 1992 All Rights Reserved
*/

/*
   Important Note:

   No method should ever dispatch a DDE message or allow a DDE message to
   be dispatched.
   Therefore, no method should ever enter a message dispatch loop.
   Also, a method should not show a dialog or message box, because the
   processing of the dialog box messages will allow DDE messages to be
   dispatched.
*/



#define SERVERONLY
#include "Windows.h"
#include "Ole.h"
#include "SrvrDemo.h"

/* AssociateClient
 * ---------------
 *
 * Add a client to the list of clients associated with an object.
 *
 * This function is necessary only because ServerDemo does not create object
 * structures as they are requested, but rather has a fixed set of objects.
 * When DocGetObject is called with a NULL object name, the entire
 * document is requested, but ServerDemo does not currently support making
 * the entire document an object, so DocGetObject returns one object.
 * That object now goes by two names: NULL and its real name.  Therefore
 * we need to keep track of both lpoleclient's that were passed to
 * DocGetObject.  Ideally, DocGetObject should always create a new OBJ
 * structure containing a pointer (or some reference) to the object's native
 * data and also containing one lpoleclient.
 *
 * LPOLECLIENT lpoleclient - the client to be associated with the object.
 * LPOBJ lpobj             - the object
 *
 * RETURNS: TRUE if successful
 *          FALSE if out of memory
 *
 * CUSTOMIZATION: Server Demo specific
 *
 */
static BOOL AssociateClient (LPOLECLIENT lpoleclient, LPOBJ lpobj)
{
   int i;
   for (i=0; i < clpoleclient; i++)
   {
      if (lpobj->lpoleclient[i]==lpoleclient)
      {
         return TRUE;
      }
      if (lpobj->lpoleclient[i]==NULL)
      {
         lpobj->lpoleclient[i]=lpoleclient;
         return TRUE;
      }
   }
   return FALSE;
}



/* CreateNewDoc
 * ------------
 *
 * If lhdoc == NULL then we must register the new document by calling
 * OleRegisterServerDoc, which will return a new handle which will be stored
 * in docMain.lhdoc.
 * Also if lhdoc==NULL then this document is being created at the request of
 * the user, not of the client library.
 *
 * LONG lhdoc      - Document handle
 * LPSTR lpszDoc   - Title of the new document
 * DOCTYPE doctype - What type of document is being created
 *
 * RETURNS: TRUE if successful, FALSE otherwise.
 *
 * CUSTOMIZATION: Re-implement
 *
 */
BOOL CreateNewDoc (LONG lhdoc, LPSTR lpszDoc, DOCTYPE doctype)
{
   int i;

   // Fill in the fields of the document structure.
   if (lhdoc == NULL)
   {
      if (OLE_OK != OleRegisterServerDoc
                     (srvrMain.lhsrvr,
                      lpszDoc,
                      (LPOLESERVERDOC) &docMain,
                      (LHSERVERDOC FAR *) &docMain.lhdoc))
         return FALSE;
   }
   else
      docMain.lhdoc = lhdoc;

   docMain.doctype      = doctype;
   docMain.oledoc.lpvtbl= &docvtbl;
   // Reset all the flags because no object numbers have been used.
   for (i=1; i <= cfObjNums; i++)
      docMain.rgfObjNums[i] = FALSE;

   fDocChanged = FALSE;

   SetTitle (lpszDoc, doctype == doctypeEmbedded);
   return TRUE;
}



/* DestroyDoc
 * ----------
 *
 * Free all memory that had been allocated for a document.
 *
 *
 * CUSTOMIZATION: Re-implement.  Your application will probably use some
 *                other method for enumerating all the objects in a document.
 *                ServerDemo enumerates the child windows, but if each object
 *                does not have its own window, this will not work.
 *
 */
void DestroyDoc (void)
{
   HWND hwnd;
   HWND hwndNext;

   // Delete all object windows.
   hwnd = SelectedObjectWindow();
   while (hwnd)
   {
      hwndNext = GetWindow (hwnd, GW_HWNDNEXT);
      // Each object window frees its own memory upon receiving WM_DESTROY.
      DestroyWindow (hwnd);
      hwnd = hwndNext;
   }

   if (docMain.aName)
   {
      GlobalDeleteAtom (docMain.aName);
      docMain.aName = NULL;
   }

   if (docMain.hpal)
      DeleteObject (docMain.hpal);
}



/* DocClose                DOCUMENT "Close" METHOD
 * --------
 *
 * The library calls this method to unconditionally close the document.
 *
 * LPOLESERVERDOC lpoledoc - The server document to close
 *
 * RETURNS: Return value from RevokeDoc.
 *
 * CUSTOMIZATION: None
 *
 */
OLESTATUS FAR PASCAL __export DocClose (LPOLESERVERDOC lpoledoc)
{
   return RevokeDoc();
}



/* DocExecute                DOCUMENT "Execute" METHOD
 * ----------
 *
 * This application does not support the execution of DDE execution commands.
 *
 * LPOLESERVERDOC lpoledoc - The server document
 * HANDLE hCommands        - DDE execute commands
 *
 * RETURNS: OLE_ERROR_COMMAND
 *
 * CUSTOMIZATION: Re-implement if your application supports the execution of
 *                DDE commands.
 *
 */
OLESTATUS FAR PASCAL __export DocExecute (LPOLESERVERDOC lpoledoc, HANDLE hCommands)
{
   return OLE_ERROR_COMMAND;
}



/* DocGetObject                DOCUMENT "GetObject" METHOD
 * ------------
 *
 * The library uses this method to get an object's structure for the
 * client.  Memory needs to be allocated and initialized here for this.
 * A NULL string indicates that the client has an embedded object
 * which was started from Create, CreateFromTemplate, or Edit, but not Open.
 *
 * First see if the object name is NULL.  If so, you would ordinarily
 * return the entire document, but Server Demo returns the selected object.
 * If the object name is not NULL, then go through the list of objects,
 * searching for one with that name.  Return an error if there is not one.
 *
 * LPOLESERVERDOC lpoledoc        - The server document
 * LPSTR lpszObjectName           - The name of the object to get data for
 * LPOLEOBJECT FAR *lplpoleobject - The object's data is put here
 * LPOLECLIENT lpoleclient        - The client structure
 *
 * RETURNS:        OLE_OK
 *                 OLE_ERROR_NAME if object not found
 *                 OLE_ERROR_MEMORY if no more memory to store lpoleclient
 *
 * CUSTOMIZATION: Re-implement.
 *                lpszObjectName == "" indicates that the whole document
 *                should be the object returned.
 *
 */
OLESTATUS FAR PASCAL __export DocGetObject
   (LPOLESERVERDOC lpoledoc, LPSTR lpszObjectName,
    LPOLEOBJECT FAR *lplpoleobject, LPOLECLIENT lpoleclient)
{
    HWND  hwnd;
    ATOM  aName;
    LPOBJ lpobj;

    if (lpszObjectName == NULL || lpszObjectName[0] == '\0')
    {
        // Return a new object or the selected object.
        hwnd = SelectedObjectWindow();
        lpobj = hwnd ? HwndToLpobj (hwnd) : CreateNewObj (FALSE);
        *lplpoleobject = (LPOLEOBJECT) lpobj;
        // Associate client with object.
        if (!AssociateClient (lpoleclient, lpobj))
            return OLE_ERROR_MEMORY;
        return OLE_OK;
    }

    if (!(aName = GlobalFindAtom (lpszObjectName)))
        return OLE_ERROR_NAME;

    hwnd = SelectedObjectWindow();

    // Go through all the child windows and find the window whose name
    // matches the given object name.

    while (hwnd)
    {
         lpobj = HwndToLpobj (hwnd);

         if (aName == lpobj->aName)
         {
            // Return the object with the matching name.
            *lplpoleobject = (LPOLEOBJECT) lpobj;
            // Associate client with the object.
            if (!AssociateClient (lpoleclient, lpobj))
               return OLE_ERROR_MEMORY;
            return OLE_OK;
         }
         hwnd = GetWindow (hwnd, GW_HWNDNEXT);
    }

 if ((DOCTYPE)((DOCPTR)lpoledoc)->doctype ==  (DOCTYPE)doctypeEmbedded)
   {
      lpobj = CreateNewObj (FALSE);
      *lplpoleobject = (LPOLEOBJECT) lpobj;

      // Associate client with object.
      if (!AssociateClient (lpoleclient, lpobj))
         return OLE_ERROR_MEMORY;
      return OLE_OK;
    }

    // Object with name lpszObjName was not found.
    return OLE_ERROR_NAME;
}

/* DocRelease                DOCUMENT "Release" METHOD
 * ----------
 *
 * The library uses this method to notify the server that a revoked
 * document has finally finished all conversations, and can be
 * destroyed.
 * It sets fWaitingForDocRelease to FALSE so a new document can be created
 * and the user can continue working.
 *
 * LPOLESERVERDOC lpoledoc	  - The server document
 *
 * RETURNS: OLE_OK
 *
 * CUSTOMIZATION: None
 *
 */
OLESTATUS FAR PASCAL __export DocRelease (LPOLESERVERDOC lpoledoc)
{
   fWaitingForDocRelease = FALSE;
   // Free all memory that has been allocated for the document.
   DestroyDoc();

   return OLE_OK;
}



/* DocSave                DOCUMENT "Save" METHOD
 * -------
 *
 * Save document to a file.
 *
 * LPOLESERVERDOC lpoledoc - The document to save
 *
 * RETURNS: OLE_OK
 *
 * CUSTOMIZATION: None
 *
 */
OLESTATUS FAR PASCAL __export DocSave (LPOLESERVERDOC lpoledoc)
{
    if (docMain.doctype == doctypeFromFile)
    {
         // No "File Save As" dialog box will be brought up because the
         // file name is already known.
         return SaveDoc() ? OLE_OK : OLE_ERROR_GENERIC;
    }
    else
      return OLE_ERROR_GENERIC;
}



/* DocSetDocDimensions        DOCUMENT "SetDocDimensions" METHOD
 * -------------------
 *
 * The library calls this method to tell the server the bounds on
 * the target device for rendering the document.
 * A call to this method is ignored for linked objects because the size of
 * a linked document depends only on the source file.
 *
 * LPOLESERVERDOC lpoledoc - The server document
 * LPRECT         lprect   - The target size in MM_HIMETRIC units
 *
 * RETURNS: OLE_OK
 *
 * CUSTOMIZATION: Re-implement
 *                How an object is sized is application-specific. (Server Demo
 *                uses MoveWindow.)
 *
 */
OLESTATUS FAR PASCAL __export DocSetDocDimensions
   (LPOLESERVERDOC lpoledoc, LPRECT lprect)
{
   if (docMain.doctype == doctypeEmbedded)
   {
      RECT rect = *lprect;

      // the units are in HIMETRIC
      rect.right   = rect.right - rect.left;
      rect.bottom  = rect.top - rect.bottom;
      HiMetricToDevice (hwndMain, (LPPOINT) &rect.right);
      MoveWindow (SelectedObjectWindow(), 0, 0,
                  rect.right + 2 * GetSystemMetrics(SM_CXFRAME),
                  rect.bottom + 2 * GetSystemMetrics(SM_CYFRAME),
                  TRUE);
      /* If for some reason your application needs to notify the client that
         the data has changed because DocSetDocDimensions has been called,
         then notify the client here.
         SendDocMsg (OLE_CHANGED);
      */
   }
   return OLE_OK;
}



/* DocSetHostNames        DOCUMENT "SetHostNames" METHOD
 * ---------------
 *
 * The library uses this method to set the name of the document
 * window.
 * All this function does is change the title bar text, although it could
 * do more if necesary.
 * This function is only called for embedded objects; linked objects
 * use their filenames for the title bar text.
 *
 * LPOLESERVERDOC lpoledoc    - The server document
 * LPSTR lpszClient           - The name of the client
 * LPSTR lpszDoc              - The client's name for the document
 *
 * RETURNS:        OLE_OK
 *
 * CUSTOMIZATION: None
 *
 */
OLESTATUS FAR PASCAL __export DocSetHostNames
   (LPOLESERVERDOC lpoledoc, LPSTR lpszClient, LPSTR lpszDoc)
{
   SetTitle (lpszDoc, TRUE);
   lstrcpy ((LPSTR) szClient, lpszClient);
   lstrcpy ((LPSTR) szClientDoc, Abbrev(lpszDoc));
   UpdateFileMenu (IDM_UPDATE);
   return OLE_OK;
}



/* DocSetColorScheme                DOCUMENT "SetColorScheme" METHOD
 * -----------------
 *
 * The client calls this method to suggest a color scheme (palette) for
 * the server to use.
 * In Server Demo the document's palette is never actually used because each
 * object has its own palette.  See ObjSetColorScheme.
 *
 * LPOLESERVERDOC lpoledoc - The server document
 * LPLOGPALETTE   lppal    - Suggested palette
 *
 * RETURNS: OLE_ERROR_PALETTE if CreatePalette fails,
 *          OLE_OK otherwise
 *
 *
 * CUSTOMIZATION: If your application supports color schemes, then this
 *                function is a good example of how to create and store
 *                a palette.
 */
OLESTATUS FAR PASCAL __export DocSetColorScheme
   (LPOLESERVERDOC lpoledoc, LPLOGPALETTE lppal)
{
   HPALETTE hpal = CreatePalette (lppal);

   if (hpal==NULL)
      return OLE_ERROR_PALETTE;

   if (docMain.hpal)
   {
      // Delete old palette
      DeleteObject (docMain.hpal);
   }
   // Store handle to new palette
   docMain.hpal = hpal;
   return OLE_OK;
}



/* RevokeDoc
 * ---------
 *
 * Call OleRevokeServerDoc.
 * If the return value is OLE_WAIT_FOR_BUSY, then set fWaitingForDocRelease
 * and enter a message-dispatch loop until fWaitingForDocRelease is reset.
 * As long as fWaitingForDocRelease is set, the user interface will be
 * disabled so that the user will not be able to manipulate the document.
 * When the DocRelease method is called, it will reset fWaitingForDocRelease,
 * allowing RevokeDoc to free the document's memory and return.
 *
 * This is essentially a way to make an asynchronous operation synchronous.
 * We need to wait until the old document is revoked before we can delete
 * its data and create a new one.
 *
 * Note that we cannot call RevokeDoc from a method because it is illegal to
 * enter a message-dispatch loop within a method.
 *
 * RETURNS: The return value of OleRevokeServerDoc.
 *
 * CUSTOMIZATION: lhdoc may need to be passed in as a parameter if your
 *                application does not have a global variable corresponding
 *                to docMain.
 *
 */
OLESTATUS RevokeDoc (void)
{
   OLESTATUS olestatus;

   if ((olestatus = OleRevokeServerDoc(docMain.lhdoc)) > OLE_WAIT_FOR_RELEASE)
      DestroyDoc();

   docMain.lhdoc = NULL; // A NULL handle indicates that the document
                         // has been revoked or is being revoked.
   return olestatus;

}



/* SaveChangesOption
 * -----------------
 *
 * Give the user the opportunity to save changes to the current document
 * before continuing.
 *
 * BOOL *pfUpdateLater - Will be set to TRUE if the client does not accept
 *                       the update and needs to be updated when the document
 *                       is closed.  In that case, OLE_CLOSED will be sent.
 *
 * RETURNS: IDYES, IDNO, or IDCANCEL
 *
 * CUSTOMIZATION: None
 *
 */
int SaveChangesOption (BOOL *pfUpdateLater)
{
   int  nReply;
   char szBuf[cchFilenameMax];

   *pfUpdateLater = FALSE;

   if (fDocChanged)
   {
       char szTmp[cchFilenameMax];

       if (docMain.aName)
           GlobalGetAtomName (docMain.aName, szTmp, cchFilenameMax);
       else
           szTmp[0] = NULL;

       if (docMain.doctype == doctypeEmbedded)
           wsprintf (szBuf, "The object has been changed.\n\nUpdate %s before closing the object?", Abbrev (szTmp));
       else
           lstrcpy (szBuf, (LPSTR) "Save changes?");

       nReply = MessageBox (hwndMain, szBuf, szAppName,
                      MB_ICONEXCLAMATION | MB_YESNOCANCEL);

       switch (nReply)
       {
          case IDYES:
              if (docMain.doctype != doctypeEmbedded)
                  SaveDoc();
              else
                  switch (OleSavedServerDoc (docMain.lhdoc))
                  {
                      case OLE_ERROR_CANT_UPDATE_CLIENT:
                          *pfUpdateLater = TRUE;
                          break;
                      case OLE_OK:
                          break;
                      default:
                          ErrorBox ("Fatal Error: Cannot update.");
                  }
              return IDYES;
          case IDNO:
              return IDNO;
         case IDCANCEL:
              return IDCANCEL;
       }
   }
   return TRUE;
}



/* SendDocMsg
 * ----------
 *
 * This function sends messages to all the objects in a document when
 * the document has changed.
 *
 * WORD wMessage - The message to send
 *
 * CUSTOMIZATION: The means of enumerating all the objects in a document
 *                is application specific.
 */
void SendDocMsg (WORD wMessage)
{
    HWND    hwnd;

    // Get handle to first object window.
    hwnd = SelectedObjectWindow();

    // Send message to all object windows.
    while (hwnd)
    {
        SendObjMsg (HwndToLpobj(hwnd), wMessage);
        hwnd = GetWindow (hwnd, GW_HWNDNEXT);
    }
}
