/*
 * CCLIENT.CPP
 * Sample Code Class Libraries
 *
 * Implementation of the CClient class that handles an SDI or MDI
 * client area window.
 *
 * Copyright (c)1993-1994 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */



#include <windows.h>
#include "classlib.h"
#include <dbgout.h>


/*
 * CClient::CClient
 * CClient::~CClient
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the application.
 */

CClient::CClient(HINSTANCE hInst)
    : CWindow(hInst)
    {
    m_pFR=NULL;
    m_cDoc=0;
    m_pDocLast=NULL;

    return;
    }


CClient::~CClient(void)
    {
    return;
    }







/*
 * CClient::FInit
 *
 * Purpose:
 *  Creates a client area window sensitive to SDI or MDI
 *  (compile-time).
 *
 * Parameters:
 *  hMenuWindow     HWND of the Window menu on the frame.
 *  pRect           LPRECT containing the desired window rectangle.
 *  pFR             PCFrame to which this client belongs.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CClient::FInit(HMENU hMenuWindow, LPRECT pRect, PCFrame pFR)
    {
    HWND                hWndFrame;
   #ifdef MDI
    CLIENTCREATESTRUCT  ccs;
   #endif

    m_pFR=pFR;

    hWndFrame=m_pFR->Window();

   #ifdef MDI

    ccs.hWindowMenu =hMenuWindow;
    ccs.idFirstChild=ID_MDICHILDMIN;

    //Create the MDI client filling the client area
    m_hWnd=CreateWindow(TEXT("mdiclient"), TEXT("mdiclient")
        , MDIS_ALLCHILDSTYLES | WS_CHILD | WS_CLIPCHILDREN
        | WS_VISIBLE | WS_CLIPSIBLINGS
        , pRect->left, pRect->top, pRect->right-pRect->left
        , pRect->bottom-pRect->top, hWndFrame, NULL, m_hInst
        , &ccs);

   #else

    //SDI case, create out own SDI Client window
    m_hWnd=CreateWindow(SZCLASSSDICLIENT, SZCLASSSDICLIENT
        , WS_CHILD | WS_CLIPCHILDREN | WS_VISIBLE | WS_CLIPSIBLINGS
        , pRect->left, pRect->top,  pRect->right-pRect->left
        , pRect->bottom-pRect->top, hWndFrame, NULL, m_hInst, 0L);

   #endif

    return (NULL!=m_hWnd);
    }





/*
 * CClient::CreateDoc (Internal)
 * CClient::CreateCDocument (Virtual)
 *
 * Purpose:
 *  Constructs a new document.  This function is overridable so an
 *  app can use the default CClient but create a derived document
 *  class.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  PCDocument      Pointer to the new document object.
 */

PCDocument CClient::CreateDoc(void)
    {
    PCDocument  pDoc;

    pDoc=CreateCDocument();
    m_pDocLast=pDoc;

    return pDoc;
    }

PCDocument CClient::CreateCDocument(void)
    {
    return new CDocument(m_hInst, m_pFR);
    }






/*
 * CClient::TranslateAccelerator
 *
 * Purpose:
 *  Provides an isolated system accelerator translation as necessary
 *  for the client window.  In MDI this calls TranslateMDISysAccel
 *  but is a NOP in SDI.
 *
 * Parameters:
 *  pMsg            LPMSG to the message from GetMessage
 *
 * Return Value:
 *  BOOL            TRUE if the accelerator was translated and
 *                  processed, FALSE otherwise.
 */

BOOL CClient::TranslateAccelerator(LPMSG pMsg)
    {
   #ifdef MDI
    return TranslateMDISysAccel(m_hWnd, pMsg);
   #else
    return FALSE;
   #endif
    }





/*
 * CClient::DefFrameProc
 *
 * Purpose:
 *  Encapsulates which default message procedure to call for a frame
 *  window: SDI (DefWindowProc) or MDI (DefFrameProc).
 *
 * Parameters:
 *  hWnd            HWND of the frame
 *  iMsg            UINT of the message
 *  wParam          WPARAM of the message
 *  lParam          LPARAM of the message
 *
 * Return Value:
 *  LRESULT         Return value for the message.
 */

LRESULT CClient::DefaultFrameProc(HWND hWnd, UINT iMsg, WPARAM wParam
    , LPARAM lParam)
    {
   #ifdef MDI
    return (DefFrameProc(hWnd, m_hWnd, iMsg, wParam, lParam));
   #else
    return (DefWindowProc(hWnd, iMsg, wParam, lParam));
   #endif
    }





/*
 * CClient::OnWindowCommand
 *
 * Purpose:
 *  Handles Window menu commands for MDI situations.  This is a NOP
 *  in SDI.
 *
 * Parameters:
 *  uCommand        UINT command to execute:
 *                      WM_MDICASCADE
 *                      WM_MDITILE
 *                      WM_MDIICONARRANGE
 *
 *  uOption         UINT optional parameter for WM_MDITILE, either
 *                  MDITILE_HORIZONTAL or MDITILE_VERTICAL.
 *
 * Return Value:
 *  None
 */

void CClient::OnWindowCommand(UINT uCommand, UINT uOption)
    {
    SendMessage(m_hWnd, uCommand, (WPARAM)uOption, 0L);
    return;
    }







/*
 * CClient::OnSize
 *
 * Purpose:
 *  Handles resizing the client window when the frame is resized.
 *
 * Parameters:
 *  x, y            UINT new location of the window.
 *  cx, cy          UINT new dimensions of the window.
 *
 * Return Value:
 *  None
 */

void CClient::OnSize(UINT x, UINT y, UINT cx, UINT cy)
    {
    SetWindowPos(m_hWnd, NULL, x, y, cx, cy
        , SWP_NOZORDER | SWP_NOACTIVATE);
    return;
    }






/*
 * CClient::NewDocument
 *
 * Purpose:
 *  Creates a new blank document in the client space.  See
 *  CloseDocument for the opposite effect.
 *
 * Parameters:
 *  fVisible        BOOL indicating if the document is to be visible
 *                  or not.
 *  pAdv            PCDocumentAdviseSink to set with the new document
 *                  for notifications.  Can be NULL.
 *
 * Return Value:
 *  PCDocument      Pointer to the new document object.
 */

PCDocument CClient::NewDocument(BOOL fVisible
    , PCDocumentAdviseSink pAdv)
    {
    MDICREATESTRUCT mcs;
    HWND            hWndDoc;
    PCDocument      pDoc;
    DOCUMENTINIT    di;
    BOOL            fCreate=TRUE;
   #ifdef MDI
    DWORD           dw;
   #endif

   #ifdef MDI
    //In MDI we create a new CDocument
    pDoc=CreateDoc();  //This could create a derived class...
   #else
    //In SDI we close the one we have and create a new one.
    pDoc=ActiveDocument();

    if (NULL!=pDoc)
        CloseDocument(pDoc);

    pDoc=CreateDoc();
   #endif

    if (fCreate)
        {
        /*
         * We implement this by having the client window actually
         * create the windows instead of using the CDocument
         * initializer.  Reason being is that we ask the client to
         * actually create the window using WM_MDICREATE (which works
         * for our SDI client as well. Since we need to conditionally
         * compile for MDI or SDI here, we create a window before
         * calling the document constructor.
         */

        mcs.szTitle=TEXT("");
        mcs.szClass=SZCLASSDOCUMENT;
        mcs.hOwner =m_hInst;
        mcs.lParam =(LPARAM)pDoc;

        mcs.x =CW_USEDEFAULT;
        mcs.cx=CW_USEDEFAULT;
        mcs.y =CW_USEDEFAULT;
        mcs.cy=CW_USEDEFAULT;

        /*
         * Set the style of the window, controlling visiblity.
         * WS_CLIPCHILDREN is important to prevent unnecessary
         * flashing of document contents that we'll usually fill
         * with some editor window.
         */
       #ifdef MDI
        mcs.style=WS_CHILD | WS_SYSMENU | WS_CAPTION
            | WS_CLIPSIBLINGS| WS_THICKFRAME | WS_MINIMIZEBOX
            | WS_MAXIMIZEBOX| WS_CLIPCHILDREN
            | ((fVisible) ? WS_VISIBLE : 0L);

        //If the current document is maxmized, maximize this one
       #ifdef WIN32
        dw=GetWindowLong(m_hWnd, GWL_STYLE);
        mcs.style |= (dw & WS_MAXIMIZE);
       #else
        dw=SendMessage(m_hWnd, WM_MDIGETACTIVE, 0, 0L);

        if (HIWORD(dw))
            mcs.style |= WS_MAXIMIZE;
       #endif //WIN32

       #else
        mcs.style=WS_CHILD | WS_CLIPCHILDREN
            | ((fVisible) ? WS_VISIBLE : 0L);
       #endif //MDI

        //Tell the Client window to create the child
        hWndDoc=(HWND)(UINT)SendMessage(m_hWnd, WM_MDICREATE, 0
            , (LONG)&mcs);


        di.idsMin=IDS_STANDARDDOCMIN;
        di.idsMax=IDS_STANDARDDOCMAX;
        di.hWndDoc=hWndDoc;
        di.pAdv=pAdv;

        if (!pDoc->FInit(&di))
            {
            SendMessage(m_hWnd, WM_MDIDESTROY, (WPARAM)hWndDoc, 0L);
            delete pDoc;
            return NULL;
            }
        }

    m_cDoc++;

    //Update menus and gizmos for the new document if visible
    if (fVisible)
        m_pFR->UpdateGizmos();

    return pDoc;
    }







/*
 * CClient::ActiveDocument
 *
 * Purpose:
 *  Returns the active document window (encapsulates WM_MDIGETACTIVE)
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  PCDocument      Pointer to the active document object.
 */

PCDocument CClient::ActiveDocument(void)
    {
    PCDocument  pDoc=NULL;
    HWND        hWnd;

    hWnd=(HWND)(UINT)SendMessage(m_hWnd, WM_MDIGETACTIVE, 0, 0L);

    if (NULL!=hWnd)
        pDoc=(PCDocument)SendMessage(hWnd, DOCM_PDOCUMENT, 0, 0L);
    else
        pDoc=m_pDocLast;

    return pDoc;
    }




/*
 * CClient::ShowDocument
 *
 * Purpose:
 *  Shows or hides a document.
 *
 * Parameters:
 *  pDoc            PCDocument to show or hide.
 *  fShow           BOOL indicating whether to show or hide the
 *                  document.
 *
 * Return Value:
 *  BOOL            Previous shown state of the document.
 */

BOOL CClient::ShowDocument(PCDocument pDoc, BOOL fShow)
    {
    BOOL        fRet;

    if (NULL==pDoc)
        return FALSE;

    fRet=IsWindowVisible(pDoc->Window());

    ShowWindow(pDoc->Window(), fShow ? SW_SHOW : SW_HIDE);

    MDIREFRESHMENU(m_hWnd);     //Macro in book1632.h
    DrawMenuBar(m_pFR->Window());

    SendMessage(m_hWnd, WM_MDIACTIVATE, (WPARAM)pDoc->Window(), 0L);

    //Update gizmos if we're *changing* a document's visibility
    if (fShow != fRet)
        m_pFR->UpdateGizmos();

    return fRet;
    }






/*
 * CClient::SDIVerify
 *
 * Purpose:
 *  In MDI, this is a NOP, but in SDI checks if the user has dirtied
 *  and wants to save the current document before blasting it away.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if the operation calling us can proceed,
 *                  FALSE to abort the operation.
 */

BOOL CClient::SDIVerify(void)
    {
   #ifdef MDI
    return TRUE;
   #else
    PCDocument      pDoc;

    pDoc=ActiveDocument();

    /*
     * In SDI, we'll erase the current, so verify if we have one
     * before continuing.  If we don't have any document, then we
     * don't have a problem.
     */

    if (NULL==pDoc)
        return TRUE;

    return FCleanVerify(pDoc);
   #endif
    }






/*
 * CClient::CloseDocument
 *
 * Purpose:
 *  Closes a document created with NewDocument.
 *
 * Parameters:
 *  pDoc            PCDocument of the document to close.
 *
 * Return Value:
 *  UINT            New count of open documents.
 */

UINT CClient::CloseDocument(PCDocument pDoc)
    {
    HWND        hWndT;

    if (NULL==pDoc)
        return m_cDoc;

    /*
     * Again, since the client window controlled document creation
     * we destroy the document window here instead of asking the
     * document to do it for us.  Once we're rid of the window then
     * we can use the destructor.
     */

    hWndT=pDoc->Window();

    //Don't delete unowned windows.
    if (GetParent(hWndT)!=m_hWnd)
        return m_cDoc;

    m_cDoc--;

    //Update window text.  Maxed MDI windows handled automatically.
   #ifdef MDI
    m_pFR->WindowTitleSet(NULL, TRUE);
   #else
    m_pFR->WindowTitleSet(NULL, FALSE);
   #endif

    if (m_pDocLast==pDoc)
        m_pDocLast=NULL;

    //Let the document clean up first, then we can nuke the window.
    SendMessage(m_hWnd, WM_MDIDESTROY, (WPARAM)hWndT, 0L);
    delete pDoc;

    return m_cDoc;
    }







/*
 * CClient::QueryCloseAllDocuments
 *
 * Purpose:
 *  Ask every document window we have in us if we can close them
 *  down.  We use this when exiting the entire application.
 *
 * Parameters:
 *  fClose          BOOL indicating if we should query close on the
 *                  documents or acutally close them.
 *
 * Return Value:
 *  BOOL            TRUE if we can close, FALSE otherwise.
 */

BOOL CClient::QueryCloseAllDocuments(BOOL fClose)
    {
    PCDocument   pDoc;
    HWND         hWndT;
    HWND         hPrevClose=NULL;

    hWndT=GetWindow(m_hWnd, GW_CHILD);

    while (NULL!=hWndT)
        {
        //Cache these: hWndT might be destroyed in closing hPrevClose
        HWND    hWndOwner=GetWindow(hWndT, GW_OWNER);
        HWND    hWndNext =GetWindow(hWndT, GW_HWNDNEXT);

        if (NULL!=hPrevClose)
            {
            pDoc=(PCDocument)SendMessage(hPrevClose, DOCM_PDOCUMENT
                , 0, 0L);
            CloseDocument(pDoc);
            hPrevClose=NULL;
            }

        //Skip icon title windows when saving hPrevClose.
        if (NULL==hWndOwner)
            {
            pDoc=(PCDocument)SendMessage(hWndT, DOCM_PDOCUMENT
                , 0, 0L);

            if (fClose)
                {
                if (!FCleanVerify(pDoc))
                    return FALSE;

                //Delay closing this.
                hPrevClose=hWndT;
                }
            else
                {
                if (0==SendMessage(hWndT, WM_QUERYENDSESSION, 0, 0L))
                    return FALSE;
                }
            }

        hWndT=hWndNext;
        }

    //Close the last window as necessary.
    if (fClose && IsWindow(hPrevClose))
        {
        pDoc=(PCDocument)SendMessage(hPrevClose, DOCM_PDOCUMENT
            , 0, 0L);
        CloseDocument(pDoc);
        hPrevClose=NULL;
        }

    return TRUE;
    }







/*
 * CClient::FCleanVerify
 *
 * Purpose:
 *  Checks if the document under scrutiny is dirty or not.  If so,
 *  then we ask the user if they want to save it.  If not, then we
 *  just return TRUE.
 *
 * Parameters:
 *  pDoc            PCDocument under consideration.
 *
 * Return Value:
 *  BOOL            TRUE if the document is clean, saved, or the user
 *                  doesn't want to save it.  FALSE if the user
 *                  presses Cancel or the document was not saved.
 */

BOOL CClient::FCleanVerify(PCDocument pDoc)
    {
    TCHAR           szFile[CCHPATHMAX];
    LPTSTR          psz=szFile;

    if (NULL==pDoc)
        return TRUE;

    //Nothing to do if we're clean.
    if (!pDoc->FDirtyGet())
        return TRUE;

    //Get the filename and send to the frame for asking the question.
    *psz=0;
    pDoc->FilenameGet(psz, CCHPATHMAX);

    return m_pFR->FAskAndSave(psz);
    }






/*
 * SDIClientWndProc
 *
 * Purpose:
 *  SDI client window class that will create one document for us,
 *  emulating the functions of an MDI client but with only one doc.
 */

LRESULT APIENTRY SDIClientWndProc(HWND hWnd, UINT iMsg
    , WPARAM wParam, LPARAM lParam)
    {
    HWND                hWndDoc;
    LPMDICREATESTRUCT   pMCS;
    RECT                rc;

    hWndDoc=(HWND)GetWindowLong(hWnd, CLIENTWL_HWNDDOC);

    switch (iMsg)
        {
        case WM_CREATE:
            SetWindowLong(hWnd, CLIENTWL_HWNDDOC, 0L);
            break;

        case WM_SIZE:
            if (NULL!=hWndDoc)
                {
                //Change the document window to match
                SetWindowPos(hWndDoc, NULL, 0,  0, LOWORD(lParam)
                    , HIWORD(lParam), SWP_NOMOVE | SWP_NOZORDER
                    | SWP_NOACTIVATE);
                }
            break;


        case WM_MDICREATE:
            pMCS=(LPMDICREATESTRUCT)lParam;

            //We only ate one *visible* document in SDI cases.
            if (NULL!=hWndDoc && (WS_VISIBLE & pMCS->style))
                return (LONG)(UINT)hWndDoc;

            /*
             * For our one visible window, we set this as the active
             * window.  For hidden windows, we return their window
             * handle but don't change the 'active' window we store.
             *
             * Note that we force SDI documents to fill the client
             * area.
             */

            GetClientRect(hWnd, &rc);

            hWndDoc=CreateWindowEx(WS_EX_NOPARENTNOTIFY
                , pMCS->szClass, pMCS->szTitle, pMCS->style
                , rc.left, rc.top, rc.right-rc.left, rc.bottom-rc.top
                , hWnd, (HMENU)ID_DOCUMENT, (HINSTANCE)pMCS->hOwner
                , pMCS);


            if (WS_VISIBLE & pMCS->style)
                {
                ShowWindow(hWndDoc, SW_SHOW);
                SetWindowLong(hWnd, CLIENTWL_HWNDDOC
                    , (LONG)(UINT)hWndDoc);
                }

            return (LONG)(UINT)hWndDoc;


        case WM_MDIACTIVATE:
            /*
             * Make the new window the active one.  The NEWMDIACTIVE
             * macro is wParam in Win16, lParam in Win32.
             */
            SetWindowLong(hWnd, CLIENTWL_HWNDDOC,(LONG)NEWMDIACTIVE);
            break;


        case WM_MDIGETACTIVE:
            return (LONG)(UINT)hWndDoc;


        case WM_MDIDESTROY:
            //The only windows we should destroy are children of us.
            if (GetParent((HWND)wParam)==hWnd)
                {
                DestroyWindow((HWND)wParam);

                /*
                 * If this is the visible window, clear out the
                 * window word.
                 */
                if ((HWND)wParam==hWndDoc)
                    SetWindowLong(hWnd, CLIENTWL_HWNDDOC, 0L);
                }
            break;

        default:
            return DefWindowProc(hWnd, iMsg, wParam, lParam);
        }

    return 0L;
    }
