/*
 * clidemo.c - OLE client application sample code
 *
 * Created by Microsoft Corporation.
 * (c) Copyright Microsoft Corp. 1990 - 1992  All Rights Reserved
 *
 */

 /***************************************************************************
 * IMPORTANT - README:
 * OLE client applications are windows programs which use the OLE client
 * APIs.  Therefore it is imperative that you understand how these APIs
 * operate. Most importantly it is essential that you keep in mind which
 * procedure calls result in asynchronous states: a state where the operation
 * is not truely complete after a return from the call.
 *
 * Many functions produce asynchronous states, for example, OleActivate,
 * OleClose, OleCopyFromLink, OleCreate ... Reference your SDK manual for
 * a complete list.
 *
 * So whenever you call any of these library functions keep in mind that
 * the operation is not necessarily complete once a return is made.
 * These operations require communications with a server application.  With
 * OLE the inter-application communication is done through DDE.  In order
 * for a DDE conversation to complete several DDE messages need to be
 * sent and recieved by both the server and client OLE DLLs.  So, the
 * asynchronous operations will not complete until the client application
 * enters a message dipatch loop.  Therefore, it is necessary to enter
 * a dispatch loop and wait for completion.  It is not necessary to block
 * all other operation; however, it is very important to coordinate the
 * user activity to prevent disastrous re-entry cases.
 *
 * In this application I have written a macro to prevent re-entry
 * problems.  Namely: ANY_OBJECT_BUSY which prevents a user from initiating
 * an action which will result in an asynchronous call if there is an object
 * already in an asynchronous state.
 *
 * The following is brief summary of the three macros:
 *
 * ANY_OBJECT_BUSY: checks to see if any object in the document is busy.
 *              This prevents a new document from being saved to file if there are
 *              objects in asynchronous states.
 *
 * So, the problem is that we have to enter a message dispatch loop in order
 * to let DDE messages get through so that asynchronous operations can finish.
 * And while we are in the message dispatch loops (WaitForObject or WaitForAllObjects)
 * we have to prevent the user from doing things that can't be done when an
 * object(s) is busy.  Yes, it is confusing , but, the end result is a super
 * cool application that can have linked and embbeded objects!
 ***************************************************************************/

//--- INCLUDES ---
//The order of the includes matters for compile efficiency ... /YX

#include <windows.h>                   //- WINDOWS
#include <ole.h>                       //- OLE structs and defines
#include "demorc.h"                    //- header for resource file
#include "global.h"                    //- global app variables

#include <shellapi.h>                  //- Shell, drag and drop headers
#include "register.h"
#include "clidemo.h"                   //- app includes:
#include "stream.h"
#include "object.h"
#include "dialog.h"
#include "utility.h"

//--- VARIABLES ---

//-- Global
BOOL              fRetry = FALSE;
HWND              hwndFrame;           //- main window
HANDLE            hAccTable;           //- accelerator table
HANDLE		  hInst;	       //- Instance Handle
char              szFrameClass[] = "CliDemo";//- main window class name
char              szItemClass[]  = "ItemClass";//- item window class name
char              szAppName[CBMESSAGEMAX];//- Application name
int               iObjects = 0;        //- object count
int               iObjectNumber = 0;   //- object number for object name
char              szFileName[CBPATHMAX];
                                       //- ClipBoard formats:
OLECLIPFORMAT     vcfLink;             //- "ObjectLink"
OLECLIPFORMAT     vcfNative;           //- "Native"
OLECLIPFORMAT     vcfOwnerLink;        //- "OwnerLink"

extern int        giXppli;             //- pixels per logical inch along width
extern int        giYppli;             //- pixels per logical inch along height

/***************************************************************************
 * WinMain() - Main Windows routine
 ***************************************************************************/

int PASCAL WinMain(                    //- ENTRY:
   HANDLE         hInstance,           //- standard windows entry
   HANDLE         hPrevInstance,
   LPSTR          lpCmdLine,
   int            nCmdShow
){
    hInst = hInstance;

    if (!hPrevInstance)
        if (!InitApplication(hInst))   //- register window classes
            return FALSE;


    if (!InitInstance(hInst))          //- create window instance
        return FALSE;

    OfnInit(hInst);                    //- setup to use <commdlg.dll>

                                       //- register clipboard formats
                                       //- used for OLE
    vcfLink      = RegisterClipboardFormat("ObjectLink");
    vcfNative    = RegisterClipboardFormat("Native");
    vcfOwnerLink = RegisterClipboardFormat("OwnerLink");


    ShowWindow(hwndFrame, nCmdShow);
    UpdateWindow(hwndFrame);
    ProcessCmdLine(lpCmdLine);

    while (ProcessMessage(hwndFrame, hAccTable)) ;

    return FALSE;
}

/***************************************************************************
 * InitApplication()
 *
 * registers the window classes used by the application.
 *
 * Returns BOOL:      - TRUE if successful.
 ***************************************************************************/

static BOOL InitApplication(           //- ENTRY:
   HANDLE         hInst                //- instance handle
){                                     //- LOCAL:
   WNDCLASS       wc;                  //- temp wind-class structure

   wc.style          = NULL;
   wc.lpfnWndProc    = FrameWndProc;
   wc.cbClsExtra     = 0;
   wc.cbWndExtra     = 0;
   wc.hInstance      = hInst;
   wc.hIcon          = LoadIcon(hInst, MAKEINTRESOURCE(ID_APPLICATION));
   wc.hCursor        = LoadCursor(NULL, IDC_ARROW);
   wc.hbrBackground  = COLOR_APPWORKSPACE + 1;
   wc.lpszMenuName   = MAKEINTRESOURCE(ID_APPLICATION);
   wc.lpszClassName  = szFrameClass;

   if (!RegisterClass(&wc))
      return FALSE;
                                       //- application item class
   wc.style          = CS_DBLCLKS | CS_VREDRAW | CS_HREDRAW;
   wc.lpfnWndProc    = ItemWndProc;
   wc.hIcon          = NULL;
   wc.cbWndExtra     = sizeof(APPITEMPTR);
   wc.lpszMenuName   = NULL;
   wc.lpszClassName  = szItemClass;

   if (!RegisterClass(&wc))
      return FALSE;

   return TRUE;

}

/***************************************************************************
 * InitInstance()
 *
 * create the main application window.
 *
 * Returns BOOL:      - TRUE if successful else FALSE.
 ***************************************************************************/

static BOOL InitInstance(              //- ENTRY:
   HANDLE	  hInst 	       //- instance handel
){
   HDC  hDC;

   hAccTable = LoadAccelerators(hInst, MAKEINTRESOURCE(ID_APPLICATION));

   if (!(hwndFrame =
      CreateWindow(
         szFrameClass, "",
         WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
         CW_USEDEFAULT, CW_USEDEFAULT,
         CW_USEDEFAULT, CW_USEDEFAULT,
         NULL,
         NULL,
         hInst,
         NULL
      )))
      return FALSE;                    //- ERROR return

   LoadString(hInst, IDS_APPNAME, szAppName, CBMESSAGEMAX);
   DragAcceptFiles(hwndFrame, TRUE);   //- allow dragged and dropped files

   // Initialize global variables with LOGPIXELSX and LOGPIXELSY

   hDC    = GetDC (NULL);       // Get the hDC of the desktop window
   giXppli = GetDeviceCaps (hDC, LOGPIXELSX);
   giYppli = GetDeviceCaps (hDC, LOGPIXELSY);
   ReleaseDC (NULL, hDC);


   return TRUE;                        //- SUCCESS return

}

/***************************************************************************
 *  ProcessCmdLine()
 *
 *  process command line getting any command arguments.
 ***************************************************************************/

void ProcessCmdLine(                   //- ENTRY:
   LPSTR          lpCmdLine            //- command line argument
){                                     //- LOCAL:
   LPSTR          lpstrExt = lpCmdLine;//- pointer to file extension
   OFSTRUCT       ofs;

   if (*lpCmdLine)
   {                                   //- look for file extension
      while (*lpstrExt && *lpstrExt != '.')
         lpstrExt = AnsiNext(lpstrExt);

      lstrcpy(szFileName, lpCmdLine);
      if (!(*lpstrExt))                //- append default extension
      {
         lstrcat(szFileName,".");
         lstrcat(szFileName,szDefExtension);
      }
                                       //- get the files fully
      OpenFile(szFileName, &ofs, OF_PARSE);//- qualified name
      lstrcpy(szFileName, ofs.szPathName);
   }
   else
      *szFileName = NULL;
                                       //- pass filename to main winproc
   SendMessage(hwndFrame,WM_INIT,NULL,(long)NULL);

}


/***************************************************************************
 *  FrameWndProc()
 *
 *  Message handler for the application frame window.
 *
 *  Returns long - Variable, depends on message.
 ***************************************************************************/

long FAR PASCAL __export FrameWndProc( //- ENTRY:
   HWND           hwnd,                //- standard wind-proc parameters
   UINT       msg,
   WPARAM	  wParam,
   LPARAM	  lParam
){                                     //- LOCAL:
                                       //- ^ Document file name
   static LHCLIENTDOC   lhcDoc;        //- Document Handle
   static LPOLECLIENT   lpClient;      //- pointer to client
   static LPAPPSTREAM   lpStream;      //- pointer to stream vtbl
   APPITEMPTR           pItem;         //- application item pointer

   switch (msg)
   {
      case WM_INIT:                    //- user defined message
         if (!InitAsOleClient(hInst, hwnd, szFileName, &lhcDoc, &lpClient, &lpStream))
            DestroyWindow(hwnd);
         break;
                                       //- the following three messages are
                                       //- used to avoid problems with OLE
                                       //- see the comment in object.h
      case WM_DELETE:                  //- user defined message
         pItem = (APPITEMPTR) wParam;  //- delete object
         WaitForObject(pItem);
         ObjDelete(pItem,DELETE);
         if (lParam)
            cOleWait--;
         break;

      case WM_ERROR:                   //- user defined message
         ErrorMessage(wParam);         //- display error message
         break;

      case WM_RETRY:                   //- user defined message
         RetryMessage((APPITEMPTR)wParam, RD_RETRY | RD_CANCEL);
         break;

      case WM_INITMENU:
         UpdateMenu((HMENU)wParam);
         break;

      case WM_COMMAND:
         pItem = GetTopItem();
         switch (wParam)
         {
            case IDM_NEW:
               ANY_OBJECT_BUSY;
               NewFile(szFileName,&lhcDoc,lpStream);
               break;

            case IDM_OPEN:
               ANY_OBJECT_BUSY;
               MyOpenFile(szFileName,&lhcDoc,lpClient,lpStream);
               break;

            case IDM_SAVE:
               ANY_OBJECT_BUSY;
               SaveFile(szFileName,lhcDoc,lpStream);
               break;

            case IDM_SAVEAS:
               ANY_OBJECT_BUSY;
               SaveasFile(szFileName,lhcDoc,lpStream);
               break;

            case IDM_ABOUT:
               AboutBox();
               break;

            case IDM_INSERT:
               ANY_OBJECT_BUSY;
               ObjInsert(lhcDoc, lpClient);
               break;

            case IDM_INSERTFILE:
               ANY_OBJECT_BUSY;
               ObjCreateFromTemplate(lhcDoc,lpClient);
               break;

            case IDM_PASTE:
            case IDM_PASTELINK:
               ANY_OBJECT_BUSY;
               ObjPaste(wParam == IDM_PASTE,lhcDoc,lpClient);
               break;

            case IDM_LINKS:
               ANY_OBJECT_BUSY;
               pItem = GetTopItem();
               LinkProperties();
               break;

            case IDM_EXIT:
               ANY_OBJECT_BUSY;
               SendMessage(hwnd, WM_SYSCOMMAND, SC_CLOSE, 0L);
               break;

            case IDM_COPY:
            case IDM_CUT:
               ANY_OBJECT_BUSY;

               if (!ObjCopy(pItem))
               {
                  ErrorMessage((wParam == IDM_CUT) ?
                     E_CLIPBOARD_CUT_FAILED : E_CLIPBOARD_COPY_FAILED);
                  break;
               }

               if (wParam == IDM_COPY)
                  break;

            case IDM_CLEAR:            //- CUT falls through to clear
               ANY_OBJECT_BUSY;
               ClearItem(pItem);
               break;

            case IDM_CLEARALL:
               ANY_OBJECT_BUSY;
               ClearAll(lhcDoc,DELETE);
               Dirty(DOC_DIRTY);
               break;

            default:
               if( (wParam >= IDM_VERBMIN) && (wParam <= IDM_VERBMAX) )
               {
                  ANY_OBJECT_BUSY;
                  ExecuteVerb(wParam - IDM_VERBMIN,pItem);
                  break;
               }
               return DefWindowProc(hwnd, msg, wParam, lParam);
         }
         break;

      case WM_DROPFILES:
         ANY_OBJECT_BUSY;
         ObjCreateWrap(wParam, lhcDoc, lpClient);
         break;

      case WM_CLOSE:
         ANY_OBJECT_BUSY;
         if (!SaveAsNeeded(szFileName, lhcDoc, lpStream))
            break;
         DeregDoc(lhcDoc);
         DestroyWindow(hwnd);
         break;

      case WM_DESTROY:
         EndStream(lpStream);
         EndClient(lpClient);
         PostQuitMessage(0);
         break;

      case WM_QUERYENDSESSION:         //- don't let windows terminate
         return (QueryEndSession(szFileName,lhcDoc, lpStream));

      default:
         return DefWindowProc(hwnd, msg, wParam, lParam);
   }
   return 0L;

}

/***************************************************************************
 * InitAsOleClient()
 *
 * Initiates the creation of stream and client vtbls.  These vtbls are very
 * important for the proper operation of this application.  The stream vtbl
 * lets the OLE librarys know where the location of the stream I/O routines
 * reside.  The stream routines are used by OleLoadFromStream and the like.
 * The client vtbl is used to hold the pointer to the CallBack function.
 * IMPORTANT: both the client and the stream structures have pointers to
 * vtbls which have the pointers to the functions.  Therefore, it is
 * necessary to allocate space for the vtbl and the client structure
 * which has the pointer to the vtbl.
 **************************************************************************/

static BOOL InitAsOleClient(           //- ENTRY:
   HANDLE         hInstance,           //- applicaion instance handle
   HWND           hwnd,                //- main window handle
   PSTR           pFileName,           //- document file name
   LHCLIENTDOC    *lhcDoc,             //- pointer to document Handle
   LPOLECLIENT    *lpClient,           //- pointer to client pointer
   LPAPPSTREAM    *lpStream            //- pointer to APPSTREAM pointer
){
                                       //- initiate client vtbl creation
   if (!(*lpClient = InitClient(hInstance)))
   {
      SendMessage(hwnd, WM_SYSCOMMAND, SC_CLOSE, 0L);
      return FALSE;                    //- ERROR return
   }
                                       //- initiate stream vtbl creation
   if (!(*lpStream = InitStream(hInstance)))
   {
      SendMessage(hwnd, WM_SYSCOMMAND, SC_CLOSE, 0L);
      return FALSE;                    //- ERROR return
   }

   if (*pFileName && RegDoc(pFileName,lhcDoc)
       && LoadFile(pFileName,*lhcDoc,*lpClient,*lpStream))
   {
      SetTitle(pFileName);
      return TRUE;                     //- SUCCESS return
   }

   NewFile(pFileName, lhcDoc, *lpStream);
   return TRUE;                        //- SUCCESS return

}                                      //- SUCCESS return

/****************************************************************************
 *  InitClient()
 *
 *  Initialize the OLE client structure, create and fill the OLECLIENTVTBL
 *  structure.
 *
 *  Returns LPOLECLIENT - if successful a pointer to a client structure
 *                        , otherwise NULL.
 ***************************************************************************/

static LPOLECLIENT InitClient(         //- ENTRY:
   HANDLE hInstance                    //- application instance handle
){                                     //- LOCAL:
   LPOLECLIENT lpClient=NULL;          //- pointer to client struct
                                       //- Allocate vtbls
   if (!(lpClient = (LPOLECLIENT)GlobalLock(
         GlobalAlloc(GMEM_FIXED | GMEM_ZEROINIT, sizeof(OLECLIENT))
      )))
      goto Error;                      //- ERROR jump

   if (!(lpClient->lpvtbl = (LPOLECLIENTVTBL)GlobalLock(
            GlobalAlloc(GMEM_FIXED | GMEM_ZEROINIT, sizeof(OLECLIENTVTBL))
      )))
      goto Error;                      //- ERROR jump
                                       //- set the CALLBACK function
                                       //- pointer
   (FARPROC)lpClient->lpvtbl->CallBack	= MakeProcInstance((FARPROC)CallBack, hInst);

   return lpClient;                    //- SUCCESS return

Error:                                 //- ERROR Tag

   ErrorMessage(E_FAILED_TO_ALLOC);
   EndClient(lpClient);                //- free any allocated space

   return NULL;                        //- ERROR return

}

/****************************************************************************
 *  InitStream()
 *
 *  Create and fill the STREAMVTBL. Create a stream structure and initialize
 *  pointer to stream vtbl.
 *
 *  Returns LPAPPSTREAM - if successful a pointer to a stream structure
 *                        , otherwise NULL .
 ***************************************************************************/

static LPAPPSTREAM InitStream(         //- ENTRY:
   HANDLE hInstance                    //- handle to application instance
){                                     //- LOCAL:
   LPAPPSTREAM lpStream = NULL;        //- pointer to stream structure

   if (!(lpStream = (LPAPPSTREAM)GlobalLock(
         GlobalAlloc(GMEM_FIXED | GMEM_ZEROINIT, sizeof(APPSTREAM))
      )))
      goto Error;                      //- ERROR jump

   if (!(lpStream->olestream.lpstbl = (LPOLESTREAMVTBL)GlobalLock(
         GlobalAlloc(GMEM_FIXED | GMEM_ZEROINIT, sizeof(OLESTREAMVTBL))
      )))
      goto Error;                      //- ERROR jump

                                       //- set stream func. pointers
   (FARPROC)lpStream->olestream.lpstbl->Get =
		      MakeProcInstance((FARPROC)ReadStream, hInst);
   (FARPROC)lpStream->olestream.lpstbl->Put =
		      MakeProcInstance((FARPROC)WriteStream, hInst);

   return lpStream;                    //- SUCCESS return

Error:                                 //- ERROR Tag

   ErrorMessage(E_FAILED_TO_ALLOC);
   EndStream(lpStream);

   return NULL;                        //- ERROR return

}

/***************************************************************************
 *  UpdateMenu()
 *
 *  Enabling or disable menuitems based upon program state.
 ***************************************************************************/

static void UpdateMenu(                //- ENTRY:
   HMENU       hMenu                   //- menu handle to updated
){                                     //- LOCAL:
   int         mf;                     //- generic menu flag
   APPITEMPTR  paItem;                 //- app item pointer
   HMENU       hSub;
                                       //- there must be at least on object
                                       //- for the following to be enabled

   paItem = GetTopItem() ;

   mf = (paItem ? MF_ENABLED : MF_GRAYED);
   EnableMenuItem(hMenu, IDM_CUT, mf); //- i.e. Cut,Copy,Clear,Clearall...
   EnableMenuItem(hMenu, IDM_COPY, mf);
   EnableMenuItem(hMenu, IDM_CLEAR, mf);
   EnableMenuItem(hMenu, IDM_CLEARALL, mf);
                                       //- enable links option only if there
                                       //- is at least one linked object
   EnableMenuItem(hMenu, IDM_LINKS, MF_GRAYED);
   for (; paItem; paItem = GetNextItem(paItem))
   {
      if (paItem->otObject == OT_LINK)
      {
         EnableMenuItem(hMenu, IDM_LINKS, MF_ENABLED);
         break;
      }
   }

   if (hSub = GetSubMenu(hMenu,POS_EDITMENU))
      UpdateObjectMenuItem(hSub);

   if (OleQueryCreateFromClip(STDFILEEDITING, olerender_draw, 0) == OLE_OK)
      EnableMenuItem(hMenu, IDM_PASTE, MF_ENABLED);
   else if (OleQueryCreateFromClip(STATICP, olerender_draw, 0) == OLE_OK)
      EnableMenuItem(hMenu, IDM_PASTE, MF_ENABLED);
   else
      EnableMenuItem(hMenu, IDM_PASTE, MF_GRAYED);

   if (OleQueryLinkFromClip(STDFILEEDITING, olerender_draw, 0) == OLE_OK)
      EnableMenuItem(hMenu, IDM_PASTELINK, MF_ENABLED);
   else
      EnableMenuItem(hMenu, IDM_PASTELINK, MF_GRAYED);

}

/***************************************************************************
 *  NewFile()
 *
 *  Save the present document and open a new blank one.
 ***************************************************************************/

static void NewFile(                   //- ENTRY:
   PSTR           pFileName,           //- open file name
   LHCLIENTDOC    *lhcptrDoc,          //- pointer to client doc. handle
   LPAPPSTREAM    lpStream             //- pointer to stream structure
){                                     //- LOCAL:
   static char  szUntitled[CBMESSAGEMAX] = "";//- "(Untitled)" string
   LHCLIENTDOC lhcDocNew;              //- handle for new doc.

   if (!(*szUntitled))
      LoadString(hInst, IDS_UNTITLED, (LPSTR)szUntitled, CBMESSAGEMAX);

   if (SaveAsNeeded(pFileName, *lhcptrDoc, lpStream))
   {                                   //- try to register new document
      if (!RegDoc(szUntitled, &lhcDocNew))
         return;                       //- before deregistring the old one
      DeregDoc(*lhcptrDoc);
      *lhcptrDoc = lhcDocNew;
      Dirty(DOC_CLEAN);                //- new document is clean
      lstrcpy(pFileName,szUntitled);
      SetTitle(pFileName);
      iObjectNumber = 0;
   }

}

/***************************************************************************
 *  MyOpenFile()
 *
 *  Open a file and load it.  Notice that the new file is loaded before
 *  the old is removed.  This is done to assure a succesful file load
 *  before removing an existing document.
 ***************************************************************************/

static void MyOpenFile(                //- ENTRY:
   PSTR           pFileName,           //- open file name
   LHCLIENTDOC    *lhcptrDoc,          //- pointer to document handle
   LPOLECLIENT    lpClient,            //- pointer to client structure
   LPAPPSTREAM    lpStream             //- pointer to stream structure
){                                     //- LOCAL:
   char           szNewFile[CBPATHMAX];//- new file name buffer
   LHCLIENTDOC    lhcDocNew;           //- handle of new document
   APPITEMPTR     pItem;               //- hold top item

   if (SaveAsNeeded(pFileName, *lhcptrDoc, lpStream))
   {
      *szNewFile = NULL;

      if (!OfnGetName(hwndFrame, szNewFile, IDM_OPEN))
         return;                       //- ERROR return

      if (!RegDoc(szNewFile,&lhcDocNew))
         return;                       //- ERROR return

      pItem = GetTopItem();
      ShowDoc(*lhcptrDoc,0);           //- make old doc objects hidden.
                                       //- try to load the new file before
      if (!LoadFile(szNewFile, lhcDocNew, lpClient, lpStream))
      {                                //- before removing the old.
         DeregDoc(lhcDocNew);          //- restore old document if new
         SetTopItem(pItem);            //- file did not load
         ShowDoc(*lhcptrDoc,1);
         return;                       //- ERROR return
      }

      DeregDoc(*lhcptrDoc);            //- deregister old document
      *lhcptrDoc = lhcDocNew;
      lstrcpy(pFileName,szNewFile);
      SetTitle(pFileName);             //- set new title
      Dirty(DOC_CLEAN);
   }

}                                      //- SUCCESS return

/***************************************************************************
 *  SaveasFile()
 *
 * Prompt the user for a new file name.  Write the document to the new
 * filename.
 ***************************************************************************/

static void SaveasFile(                //- ENTRY:
   PSTR           pFileName,           //- old filename
   LHCLIENTDOC    lhcDoc,              //- document handle
   LPAPPSTREAM    lpStream             //- pointer to stream structure
){
   char           szNewFile[CBPATHMAX];//- new file name

   *szNewFile = NULL;                  //- prompt user for new file name
   if (!OfnGetName(hwndFrame, szNewFile, IDM_SAVEAS))
      return;                          //- ERROR return
                                       //- rename document
   if (!SaveFile(szNewFile, lhcDoc, lpStream))
      return;

   if (Error(OleRenameClientDoc(lhcDoc, szNewFile)))
   {
      ErrorMessage(W_FAILED_TO_NOTIFY);
      return;                          //- ERROR return
   }

   lstrcpy(pFileName,szNewFile);
   SetTitle(pFileName);

}                                      //- SUCCESS return

/***************************************************************************
 *  SaveFile()
 *
 * Save a compound document file.  If the file is untitled, ask the user
 * for a name and save the document to that file.
 ***************************************************************************/

static BOOL SaveFile(                  //- ENTRY:
   PSTR           pFileName,           //- file to save document to
   LHCLIENTDOC    lhcDoc,              //- OLE document handle
   LPAPPSTREAM    lpStream             //- pointer to app. stream struct
){                                     //- LOCAL:
   char           szNewFile[CBPATHMAX];//- New file name strings
   char           szOemFileName[2*CBPATHMAX];
   static char    szUntitled[CBMESSAGEMAX] = "";
   HANDLE         fh;                  //- file handle

   *szNewFile = NULL;
   if (!(*szUntitled))
      LoadString(hInst, IDS_UNTITLED, (LPSTR)szUntitled, CBMESSAGEMAX);

   if (!lstrcmp(szUntitled, pFileName))//- get filename for the untitled case
   {
      if (!OfnGetName(hwndFrame, szNewFile, IDM_SAVEAS))
         return FALSE;                 //- CANCEL return
      lstrcpy(pFileName,szNewFile);
      SetTitle(pFileName);
   }

   AnsiToOem(pFileName, szOemFileName);
   if ((fh =_lcreat((LPSTR)szOemFileName, 0)) <= 0)
   {
      ErrorMessage(E_INVALID_FILENAME);
      return FALSE;                    //- ERROR return
   }

   lpStream->fh = fh;
                                       //- save file on disk
   if (!WriteToFile(lpStream))
   {
      _lclose(fh);
      ErrorMessage(E_FAILED_TO_SAVE_FILE);
      return FALSE;                    //- ERROR return
   }
   _lclose(fh);

   if (Error(OleSavedClientDoc(lhcDoc)))
   {
      ErrorMessage(W_FAILED_TO_NOTIFY);
      return FALSE;                    //- ERROR return
   }

   Dirty(DOC_CLEAN);
   return TRUE;                        //- SUCCESS return

}

/***************************************************************************
 *  LoadFile()
 *
 *  Load a document file from disk.
 ***************************************************************************/

static BOOL LoadFile(                  //- ENTRY:
   PSTR           pFileName,           //- file name
   LHCLIENTDOC    lhcDoc,              //- document handle
   LPOLECLIENT    lpClient,            //- pointer to client structure
   LPAPPSTREAM    lpStream             //- pointer to stream structure
){                                     //- LOCAL:
                                       //- OEM file name
   char           szOemFileName[2*CBPATHMAX];
   HANDLE         fh;                  //- file handle
   int iObjectNumberHold;              //- hold object number

   AnsiToOem(pFileName, szOemFileName);
   if ((fh = _lopen(szOemFileName, OF_READ | OF_SHARE_DENY_WRITE)) == -1)
   {
      ErrorMessage(E_FAILED_TO_READ_FILE);
      return FALSE;                    //- ERROR return
   }

   lpStream->fh = fh;

   iObjectNumberHold = iObjectNumber;  //- save object number so it can
   iObjectNumber     = 0;              //- be restored if read from file
                                       //- fails
   if (!ReadFromFile(lpStream, lhcDoc, lpClient))
   {
      _lclose(fh);
      ErrorMessage(E_FAILED_TO_READ_FILE);
      iObjectNumber = iObjectNumberHold;
      return FALSE;                    //- ERROR return
   }
   return TRUE;                        //- SUCCESS return

}

/***************************************************************************
 *  RegDoc()
 *
 * Register the client document with the OLE library.
 **************************************************************************/

static BOOL RegDoc(                    //- ENTRY:
   PSTR           pFileName,           //- file name
   LHCLIENTDOC    *lhcptrDoc           //- pointer to client document handle
){

   if (Error(OleRegisterClientDoc(szAppName, (LPSTR)pFileName, 0L, lhcptrDoc)))
   {
      ErrorMessage(W_FAILED_TO_NOTIFY);
      return FALSE;                    //- ERROR return
   }
   return TRUE;                        //- SUCCESS return

}

/****************************************************************************
 *  DeregDoc()
 *
 *  This function initiates the removal of all OLE objects from the
 *  current document and deregisters the document with the OLE library.
 ***************************************************************************/

static void DeregDoc(                  //- ENTRY:
   LHCLIENTDOC    lhcDoc               //- client document handle
){

    if (lhcDoc)
    {                                  //- release all OLE objects
        ClearAll(lhcDoc,RELEASE);      //- and remove them from the screen
        WaitForAllObjects();
        if (Error(OleRevokeClientDoc(lhcDoc)))
            ErrorMessage(W_FAILED_TO_NOTIFY);
    }

}                                      //- SUCCESS return

/***************************************************************************
 *  ClearAll()
 *
 * This function will destroy all of the item windows in the current
 * document and delete all OLE objects.  The loop is basically an enum
 * of all child windows.
 **************************************************************************/

static void ClearAll(                  //- ENTRY:
   LHCLIENTDOC    lhcDoc,              //- application document handle
   BOOL           fDelete              //- Delete / Release
){                                     //- LOCAL:
   APPITEMPTR     pItemNext;           //- working handles
   APPITEMPTR     pItem;               //- pointer to application item

   pItem = GetTopItem();

   while (pItem)
   {
      pItemNext = GetNextItem(pItem);
      if (pItem->lhcDoc == lhcDoc)
         ObjDelete(pItem, fDelete);
      pItem = pItemNext;
   }

}
                                    //- SUCCESS return
/***************************************************************************
 * ClearItem()
 *
 * This function will destroy an item window, and make the
 * next window active.
 **************************************************************************/

void  FAR ClearItem(                 //- ENTRY:
   APPITEMPTR     pItem                //- application item pointer
){

   pItem->fVisible = FALSE;
   SetTopItem(GetNextActiveItem());
   ObjDelete(pItem, DELETE);
   Dirty(DOC_DIRTY);

}

/****************************************************************************
 *  SaveAsNeeded()
 *
 *  This function will have the file saved if and only
 *  if the document has been modified. If the fDirty flag has
 *  been set to TRUE, then the document needs to be saved.
 *
 *  Returns: BOOL -  TRUE if document doesn't need saving or if the
 *                   document has been saved successfully.
 ***************************************************************************/

static BOOL SaveAsNeeded(              //- ENTRY:
   PSTR           pFileName,           //- file to save
   LHCLIENTDOC    lhcDoc,              //- OLE doc handle
   LPAPPSTREAM    lpStream             //- pointer to OLE stream vtbl ...
){                                     //- LOCAL:
   char           sz[CBMESSAGEMAX];    //- work strings
   char           sz2[CBMESSAGEMAX + CBPATHMAX];

   if (Dirty(DOC_QUERY))               //- if doc is clean don't bother
   {

      LoadString(hInst, IDS_MAYBESAVE, sz, CBMESSAGEMAX);
      wsprintf(sz2, sz, (LPSTR)pFileName );

      switch (MessageBox(hwndFrame, sz2, szAppName, MB_YESNOCANCEL | MB_ICONQUESTION))
      {

         case IDCANCEL:
            return FALSE;              //- CANCEL return

         case IDYES:
            return (SaveFile(pFileName,lhcDoc,lpStream));

         default:
            break;
      }
   }
   return TRUE;                        //- SUCCESS return

}

/****************************************************************************
 *  SetTitle()
 *
 *  Set the window caption to the current file name. If szFileName is
 *  NULL, the caption will be set to "(Untitled)".
 ***************************************************************************/

static void SetTitle(                  //- ENTRY:
   PSTR           pFileName            //- file name
){                                     //- LOCAL
                                       //- window title string
   char           szTitle[CBMESSAGEMAX + CBPATHMAX];

   wsprintf(szTitle, "%s - %s", (LPSTR)szAppName, (LPSTR)pFileName);
   SetWindowText(hwndFrame, szTitle);

}

/***************************************************************************
 *  EndClient()
 *
 *  Perform cleanup prior to app termination. The OLECLIENT
 *  memory blocks and procedure instance thunks freed.
 **************************************************************************/

static void EndStream(                 //- ENTRY:
   LPAPPSTREAM    lpStream             //- pointer to stream structure
){                                     //- LOCAL:
   HANDLE         hGeneric;            //- temp handle

    if (lpStream)                      //- is there a STREAM struct?
    {
      if (lpStream->olestream.lpstbl)
      {
         FreeProcInstance((FARPROC)lpStream->olestream.lpstbl->Get);
         FreeProcInstance((FARPROC)lpStream->olestream.lpstbl->Put);
         hGeneric = (HANDLE)GlobalHandle(HIWORD(lpStream->olestream.lpstbl));
         GlobalUnlock(hGeneric);
         GlobalFree(hGeneric);
      }
      hGeneric = (HANDLE)GlobalHandle(HIWORD(lpStream));
      GlobalUnlock(hGeneric);
      GlobalFree(hGeneric);
    }
    if (lpfnTimerProc)
        FreeProcInstance(lpfnTimerProc);

}                                      //- SUCCESS return

/***************************************************************************
 *  EndClient()
 *
 *  Perform cleanup prior to app termination. The OLECLIENT
 *  memory blocks and procedure instance thunks are freed.
 **************************************************************************/

static void EndClient(                 //- ENTRY:
   LPOLECLIENT    lpClient             //- pointer to client structure
){                                     //- LOCAL:
   HANDLE         hGeneric;            //- temp handle

   if (lpClient)                       //- is there a client structure
   {
      if (lpClient->lpvtbl)
      {
         FreeProcInstance(lpClient->lpvtbl->CallBack);
         hGeneric = (HANDLE)GlobalHandle(HIWORD(lpClient->lpvtbl));
         GlobalUnlock(hGeneric);
         GlobalFree(hGeneric);
      }
      hGeneric = (HANDLE)GlobalHandle(HIWORD(lpClient));
      GlobalUnlock(hGeneric);
      GlobalFree(hGeneric);
   }

}                                      //- SUCCESS return

/****************************************************************************
 * QueryEndSession()
 ***************************************************************************/

static long QueryEndSession(           //- ENTRY:
   PSTR           pFileName,           //- document name
   LHCLIENTDOC    lhcDoc,              //- client document handle
   LPAPPSTREAM    lpStream             //- application stream pointer
){                                     //- LOCAL:
   APPITEMPTR     pItem;               //- application item pointer


   for (pItem = GetTopItem(); pItem; pItem = GetNextItem(pItem))
      if (OleQueryOpen(pItem->lpObject) == OLE_OK)
      {
         MessageBox(hwndFrame,"Exit CliDemo before closing Windows",
               szAppName, MB_OK | MB_ICONSTOP);
         return 0L;
      }

   if (!SaveAsNeeded(pFileName, lhcDoc, lpStream))
      return 0L;
   DeregDoc(lhcDoc);
   return 1L;

}
