/*+==========================================================================
  File:      GUIBALL.CPP

  Summary:   Implementation file for the CGuiBall C++ class. A GuiBall is
             a C++ object that displays a moving and bouncing ball in the
             client area of a designated window.  It is anchored to the
             Windows GUI (Graphical User Interface) environment. This
             GuiBall object continuously paints a ball image based on data
             it obtains from a virtual ball object. This virtual ball
             object is instantiated as a COM object (a COBall) in a
             separate thread-safe In-process server, CONSERVE.

             GuiBall relies on the system timer to send periodic WM_TIMER
             messages to a window procedure designated in the CGuiBall::Init
             call. When these messages are received, the window procedure
             calls the CGuiBall::PaintBall method which both moves and
             displays the ball image.

             For a comprehensive tutorial code tour of GUIBALL's contents
             and offerings see the accompanying CONCLIEN.TXT file. For more
             specific technical details on the internal workings see the
             comments dispersed throughout the GUIBALL source code.

  Classes:   CGuiBall.

  Origin:    5-30-96: atrent - Editor inheritance from GUIBALL.CPP in the
             FRECLIEN source.

----------------------------------------------------------------------------
  This file is part of the Microsoft OLE Tutorial Code Samples.

  Copyright (C) Microsoft Corporation, 1996.  All rights reserved.

  This source code is intended only as a supplement to Microsoft
  Development Tools and/or on-line documentation.  See these other
  materials for detailed information regarding Microsoft code samples.

  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
  PARTICULAR PURPOSE.
==========================================================================+*/

/*--------------------------------------------------------------------------
  We include WINDOWS.H for all Win32 applications.
  We include OLE2.H because we will be making calls to the OLE libraries.
  We include OLECTL.H because it has definitions for connectable objects.
  We include APPUTIL.H because we will be building this application using
    the convenient Virtual Window and Dialog classes and other
    utility functions in the APPUTIL Library (ie, APPUTIL.LIB).
  We include IBALL.H and BALLGUID.H for the common Ball-related Interface
    class, GUID, and CLSID specifications.
  We include GUIBALL.H because it has the C++ class used for GUI display
    of the moving ball.
  We include CONCLIEN.H because it has class and resource definitions
    specific to this CONCLIEN application.
---------------------------------------------------------------------------*/
#include <windows.h>
#include <ole2.h>
#include <olectl.h>
#include <apputil.h>
#include <iball.h>
#include <ballguid.h>
#include "guiball.h"
#include "sink.h"
#include "conclien.h"


/*M+M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M
  Method:   CGuiBall::CGuiBall

  Summary:  Constructor.

  Args:     void

  Modifies: ...

  Returns:  void
M---M---M---M---M---M---M---M---M---M---M---M---M---M---M---M---M---M---M-M*/
CGuiBall::CGuiBall(void)
{
  m_hWnd       = 0;
  m_pIBall     = NULL;
  m_crColor    = RGB(0,0,0);
  m_OldPos.x   = 0;
  m_OldPos.y   = 0;
  m_OldExt.x   = 0;
  m_OldExt.y   = 0;
  m_pCOBallSink = NULL;
  m_dwBallSink = 0;
  m_dwBounceSndDur = BOUNCE_SOUND_DURATION;
  m_dwBounceBotFreq = BOUNCE_BOTTOM_FREQUENCY;
  m_dwBounceSideFreq = BOUNCE_SIDE_FREQUENCY;
  m_dwBounceTopFreq = BOUNCE_TOP_FREQUENCY;
}


/*M+M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M
  Method:   CGuiBall::~CGuiBall

  Summary:  Destructor.

  Args:     void

  Modifies: ...

  Returns:  void
M---M---M---M---M---M---M---M---M---M---M---M---M---M---M---M---M---M---M-M*/
CGuiBall::~CGuiBall(void)
{
  BOOL bOk = TRUE;

  // Kill the client's app timer that drives repaints. Stops the WM_TIMER
  // messages to the display window.
  KillTimer(m_hWnd, 1);

  if (m_pIBall)
  {
    // Call down to the server's COBall and tell it about the shutdown.
    // This sets COBall's m_bAlive to FALSE and thus neutralizes all
    // subsequent Move calls and thus prevents any more NotifySinks calls
    // from within the Move method.
    m_pIBall->Move(FALSE);

    // Disconnect all Sinks--currently only one: BallSink. This officially
    // stops all BallSink notifications.
    DisconnectBallSink();

    // Release the reference to the BallSink object.
    RELEASE_INTERFACE(m_pCOBallSink);

    // Release the main interface pointer copy held in CGuiBall.
    RELEASE_INTERFACE(m_pIBall);
  }
}


/*M+M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M
  Method:   CGuiBall::Init

  Summary:  Get everything related to CGuiBall started. Make any
            subordinate objects, like COBall, and get it started. Starts
            the system timer that keeps the virtual ball moving with GUI
            painted images.

  Args:     HWND hWnd
              Handle of the display window. Part of what makes CGuiBall
              a GUI kind of thing.

  Modifies: ...

  Returns:  BOOL
              TRUE for success; FALSE for fail.
M---M---M---M---M---M---M---M---M---M---M---M---M---M---M---M---M---M---M-M*/
BOOL CGuiBall::Init(
       HWND hWnd)
{
  BOOL bOk = FALSE;
  HRESULT hr;
  COBallSink* pCob = NULL;

  if (hWnd)
  {
    m_hWnd = hWnd;

    // Call OLE service to create a COBall instance. We are not
    // aggregating it so we ask for its IBall interface directly.
    hr = CoCreateInstance(
           CLSID_DllSndBall,
           NULL,
           CLSCTX_INPROC_SERVER,
           IID_IBall,
           (PPVOID)&m_pIBall);
    if (SUCCEEDED(hr))
    {
      // Create the COBallSink Sink object to receive COBall events.
      pCob = new COBallSink(NULL, this);
      if (NULL != pCob)
      {
        // Save a pointer to the COBall IUnknown interface. AddRef
        // because of this saved copy.
        m_pCOBallSink = pCob;
        m_pCOBallSink->AddRef();
      }
      else
        hr = E_OUTOFMEMORY;

      if (SUCCEEDED(hr))
      {
        // Set up the client process to periodically move & paint the ball
        // thru WM_TIMER messages to the specified hWnd's Window proc.
        SetTimer(hWnd, 1, BALL_PAINT_DELAY, NULL);
        bOk = TRUE;
      }
    }
  }

  return (bOk);
}


/*M+M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M
  Method:   CGuiBall::PaintBall

  Summary:  Tell CGuiBall to move one increment and paint one image of the
            GuiBall.

  Args:     void

  Modifies: ...

  Returns:  void
M---M---M---M---M---M---M---M---M---M---M---M---M---M---M---M---M---M---M-M*/
void CGuiBall::PaintBall(void)
{
  HDC       hDC;
  HBRUSH    hBrush;
  POINT     Org, Ext;
  HRGN      hTmp, hOld, hNew;

  if (m_pIBall)
  {
    // Move the ball before painting it. By placing this Move call here
    // we link moving with painting in synchronized lock step.
    m_pIBall->Move(TRUE);

    // Ask the COBall for its current ball (location, region, and color).
    m_pIBall->GetBall(&Org, &Ext, &m_crColor);

    // Create the old ball image/region.
    hOld = CreateEllipticRgn(m_OldPos.x, m_OldPos.y, m_OldExt.x, m_OldExt.y);

    // Create the new ball image/region.
    hNew = CreateEllipticRgn(Org.x, Org.y, Ext.x, Ext.y);

    // Remember the new Pos and Ext as the next old set of Pos and Ext.
    m_OldPos.x = Org.x;
    m_OldPos.y = Org.y;
    m_OldExt.x = Ext.x;
    m_OldExt.y = Ext.y;

    if(hDC = GetDC(m_hWnd))
    {
      // Erase old ball image.
      hTmp = CreateRectRgn(1, 1, 2, 2);
      CombineRgn(hTmp, hOld, hNew, RGN_DIFF);
      FillRgn(hDC, hTmp, GETCLASSBRUSH(m_hWnd));

      // Make a paint brush, dip it in pixel paint, and paint the
      // new Ball image. Destroy brush after use.
      hBrush = CreateSolidBrush(m_crColor);
      FillRgn(hDC, hNew, hBrush);
      DeleteObject(hBrush);
      ReleaseDC(m_hWnd, hDC);
      DeleteObject(hTmp);
    }

    // Delete the region objects.
    DeleteObject(hOld);
    DeleteObject(hNew);
  }

  return;
}


/*M+M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M
  Method:   CGuiBall::Restart

  Summary:  Restart the display process. Places Ball in start position
            in a clean window.

  Args:     void.

  Modifies: ...

  Returns:  void.
M---M---M---M---M---M---M---M---M---M---M---M---M---M---M---M---M---M---M-M*/
void CGuiBall::Restart(void)
{
  RECT  WinRect;
  HDC   hDC = GetDC(m_hWnd);

  if(hDC && m_pIBall)
  {
    // Clear the window.
    // Get our window's client area rectangle.
    GetClientRect(m_hWnd, &WinRect);
    // Fill that rectangle with pixels of white paint.
    FillRect(hDC, &WinRect, GETCLASSBRUSH(m_hWnd));

    // Tell the COBall to reset itself.
    m_pIBall->Reset(&WinRect, 0);

    // Call our own CGuiBall method to paint an initial image of the ball.
    PaintBall();

    // Release the Device Context.
    ReleaseDC(m_hWnd, hDC);
  }

  return;
}


/*M+M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M
  Method:   CGuiBall::PaintWin

  Summary:  Clears window background and paints the GuiBall at its
            current location.

  Args:     void

  Modifies: ...

  Returns:  void
M---M---M---M---M---M---M---M---M---M---M---M---M---M---M---M---M---M---M-M*/
void CGuiBall::PaintWin(void)
{
  HDC         hDC;
  PAINTSTRUCT ps;
  RECT        WinRect;

  if(hDC = BeginPaint(m_hWnd, &ps))
    EndPaint(m_hWnd, &ps);

  if(hDC = GetDC(m_hWnd))
  {
    // Get our window's client area rectangle.
    GetClientRect(m_hWnd, &WinRect);
    // Fill that rectangle with pixels of white paint.
    FillRect(hDC, &WinRect, GETCLASSBRUSH(m_hWnd));

    // Paint a current image of the ball wherever it is.
    PaintBall();

    ReleaseDC(m_hWnd, hDC);
  }

  return;
}


/*M+M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M
  Method:   CGuiBall::BounceBottom

  Summary:  Produce a sound for the ball bounce against the Bottom
            boundary.

  Args:     void

  Modifies: ...

  Returns:  void
M---M---M---M---M---M---M---M---M---M---M---M---M---M---M---M---M---M---M-M*/
void CGuiBall::BounceBottom(void)
{
  // Use the Win32 Beep call.
  Beep(m_dwBounceBotFreq, m_dwBounceSndDur);

  return;
}


/*M+M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M
  Method:   CGuiBall::BounceSide

  Summary:  Produce a sound for the ball bounce against a Side boundary.

  Args:     void

  Modifies: ...

  Returns:  void
M---M---M---M---M---M---M---M---M---M---M---M---M---M---M---M---M---M---M-M*/
void CGuiBall::BounceSide(void)
{
  // Use the Win32 Beep call.
  Beep(m_dwBounceSideFreq, m_dwBounceSndDur);

  return;
}


/*M+M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M
  Method:   CGuiBall::BounceTop

  Summary:  Produce a sound for the ball bounce against the Top boundary.

  Args:     void

  Modifies: ...

  Returns:  void
M---M---M---M---M---M---M---M---M---M---M---M---M---M---M---M---M---M---M-M*/
void CGuiBall::BounceTop(void)
{
  // Use the Win32 Beep call.
  Beep(m_dwBounceTopFreq, m_dwBounceSndDur);

  return;
}


/*M+M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M
  Method:   CGuiBall::GetConnectionPoint

  Summary:  Internal private method to obtain a connection point interface.

  Args:     REFIID riid
              IID of the requested connection point Interface.

  Modifies: ...

  Returns:  IConnectionPoint*
              Requested IConnectionPoint interface pointer. NULL if none.
M---M---M---M---M---M---M---M---M---M---M---M---M---M---M---M---M---M---M-M*/
IConnectionPoint* CGuiBall::GetConnectionPoint(
                    REFIID riid)
{
  IConnectionPointContainer* pConnPointContainer = NULL;
  IConnectionPoint* pConnPoint = NULL;
  IConnectionPoint* pConnPt = NULL;
  HRESULT hr;

  // First query the object for its Connection Point Container. This
  // essentially asks the object in the server if it is connectable.
  hr = m_pIBall->QueryInterface(
         IID_IConnectionPointContainer,
         (PPVOID)&pConnPointContainer);
  if SUCCEEDED(hr)
  {
    // Find the requested Connection Point. This AddRef's the
    // returned pointer.
    hr = pConnPointContainer->FindConnectionPoint(riid, &pConnPt);
    if (SUCCEEDED(hr))
      pConnPoint = pConnPt;

    // Release the connection point container. We're done with it.
    RELEASE_INTERFACE(pConnPointContainer);
  }

  return pConnPoint;
}


/*M+M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M
  Method:   CGuiBall::ConnectBallSink

  Summary:  Connect the BallSink to the server COBall source.

  Args:     void

  Modifies: ...

  Returns:  HRESULT
              Standard OLE result code. NOERROR for success.
M---M---M---M---M---M---M---M---M---M---M---M---M---M---M---M---M---M---M-M*/
HRESULT CGuiBall::ConnectBallSink(void)
{
  HRESULT hr = E_FAIL;
  DWORD dwKey;
  IConnectionPoint* pConnPoint = NULL;

  if (!m_dwBallSink)
  {
    // Get the Ball Sink connection point.
    pConnPoint = GetConnectionPoint(IID_IBallSink);
    if (NULL != pConnPoint)
    {
      // Connect the object in the server to the Ball Sink in this client.
      hr = pConnPoint->Advise(m_pCOBallSink, &dwKey);
      if (SUCCEEDED(hr))
        m_dwBallSink = dwKey;

      // Release the connection point. We're done with it.
      RELEASE_INTERFACE(pConnPoint);
    }
  }

  return hr;
}


/*M+M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M+++M
  Method:   CGuiBall::DisconnectBallSink

  Summary:  Disconnect the BallSink from the server COBall source.

  Args:     void.

  Modifies: ...

  Returns:  HRESULT
              Standard OLE result code. NOERROR for success.
M---M---M---M---M---M---M---M---M---M---M---M---M---M---M---M---M---M---M-M*/
HRESULT CGuiBall::DisconnectBallSink(void)
{
  HRESULT hr = E_FAIL;
  IConnectionPoint* pConnPoint;

  if (m_dwBallSink)
  {
    // Get the Ball Sink connection point.
    pConnPoint = GetConnectionPoint(IID_IBallSink);
    if (NULL != pConnPoint)
    {
      // Disconnect the object in the server from the Ball Sink in
      // this client.
      hr = pConnPoint->Unadvise(m_dwBallSink);
      if (SUCCEEDED(hr))
        m_dwBallSink = 0;

      // Release the connection point. We're done with it.
      RELEASE_INTERFACE(pConnPoint);
    }
  }

  return hr;
}
