// This is a part of the Microsoft Foundation Classes C++ library.
// Copyright (C) 1992-1995 Microsoft Corporation
// All rights reserved.
//
// This source code is only intended as a supplement to the
// Microsoft Foundation Classes Reference and related
// electronic documentation provided with the library.
// See these sources for detailed information regarding the
// Microsoft Foundation Classes product.

#include "stdafx.h"

#ifdef AFX_OLE4_SEG
#pragma code_seg(AFX_OLE4_SEG)
#endif

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

#define new DEBUG_NEW

/////////////////////////////////////////////////////////////////////////////
// COleMessageFilter::IMessageFilter implementation

#ifdef AFX_INIT_SEG
#pragma code_seg(AFX_INIT_SEG)
#endif

COleMessageFilter::COleMessageFilter()
{
	// begin in not-busy state
	m_nBusyCount = 0;

	// dialogs are enabled by default
	m_bEnableBusy = TRUE;
	m_bEnableNotResponding = TRUE;

	m_nBusyReply = SERVERCALL_RETRYLATER;
		// effective only when m_nBusyCount != 0

	m_nRetryReply = 10000;	// default is 10 sec
	m_nTimeout = 8000;	// default is 8 sec

	m_bUnblocking = FALSE;
		// TRUE to avoid re-entrancy when busy dialog is up

	m_bRegistered = FALSE;

	ASSERT_VALID(this);
}

#ifdef AFX_TERM_SEG
#pragma code_seg(AFX_TERM_SEG)
#endif

COleMessageFilter::~COleMessageFilter()
{
	ASSERT_VALID(this);

	Revoke();
}

/////////////////////////////////////////////////////////////////////////////
// Busy state management

#ifdef AFX_OLE4_SEG
#pragma code_seg(AFX_OLE4_SEG)
#endif

void COleMessageFilter::BeginBusyState()
{
	ASSERT_VALID(this);
	++m_nBusyCount;
}

void COleMessageFilter::EndBusyState()
{
	ASSERT_VALID(this);
	if (m_nBusyCount != 0)
		--m_nBusyCount;
}

/////////////////////////////////////////////////////////////////////////////
// COleMessageFilter operations

#ifdef AFX_INIT_SEG
#pragma code_seg(AFX_INIT_SEG)
#endif

BOOL COleMessageFilter::Register()
{
	ASSERT_VALID(this);
	ASSERT(!m_bRegistered); // calling Register twice?

	if (::CoRegisterMessageFilter(&m_xMessageFilter, NULL) == S_OK)
	{
		m_bRegistered = TRUE;
		return TRUE;
	}
	return FALSE;
}

#ifdef AFX_TERM_SEG
#pragma code_seg(AFX_TERM_SEG)
#endif

void COleMessageFilter::Revoke()
{
	ASSERT_VALID(this);

	if (m_bRegistered)
	{
		::CoRegisterMessageFilter(NULL, NULL);
		m_bRegistered = FALSE;
	}
}

/////////////////////////////////////////////////////////////////////////////
// COleMessageFilter standard implementation of callbacks

#ifdef AFX_OLE4_SEG
#pragma code_seg(AFX_OLE4_SEG)
#endif

BOOL COleMessageFilter::OnMessagePending(const MSG* pMsg)
{
	// By default we rely on OLE's default message handling for every message
	//  except WM_PAINT messages.  WM_PAINT messages should not generate
	//  out-going calls.

	BOOL bEatMessage = FALSE;

	switch (pMsg->message)
	{
	case WM_PAINT:
		DispatchMessage(pMsg);
		bEatMessage = TRUE;
		break;
	}

	if (bEatMessage)
	{
		MSG msg;
		::PeekMessage(&msg, NULL, pMsg->message, pMsg->message,
			PM_REMOVE|PM_NOYIELD);
	}
	return bEatMessage;
}

BOOL COleMessageFilter::IsSignificantMessage(MSG*)
{
	// check for "significant" messages in the queue
	UINT rgnMsgs[] =
	{
		WM_KEYDOWN, WM_SYSKEYDOWN,
		WM_LBUTTONDOWN, WM_RBUTTONDOWN, WM_MBUTTONDOWN,
		WM_NCLBUTTONDOWN, WM_NCRBUTTONDOWN, WM_NCMBUTTONDOWN,
		WM_LBUTTONDBLCLK, WM_RBUTTONDBLCLK, WM_MBUTTONDBLCLK,
		WM_NCLBUTTONDBLCLK, WM_NCRBUTTONDBLCLK, WM_NCMBUTTONDBLCLK
	};
	MSG msg;
	for (int i = 0; i < _countof(rgnMsgs); i++)
	{
		if (::PeekMessage(&msg, NULL, rgnMsgs[i], rgnMsgs[i],
			PM_NOREMOVE|PM_NOYIELD))
		{
			if ((msg.message == WM_KEYDOWN || msg.message == WM_SYSKEYDOWN) &&
				(HIWORD(msg.lParam) & KF_REPEAT))
			{
				// a key-repeat is a non-significant message
				continue;
			}

			// "significant" message is waiting in the queue
			return TRUE;
		}
	}

	// no significant messages in the queue
	return FALSE;
}

int COleMessageFilter::OnBusyDialog(HTASK htaskBusy)
{
	COleBusyDialog dlg(htaskBusy, FALSE);

	int nResult = -1;
	TRY
	{
		if (dlg.DoModal() == IDOK)
			nResult = dlg.GetSelectionType();
	}
	END_TRY

	return nResult;
}

int COleMessageFilter::OnNotRespondingDialog(HTASK htaskBusy)
{
	COleBusyDialog dlg(htaskBusy, TRUE);

	int nResult = -1;
	TRY
	{
		if (dlg.DoModal() == IDOK)
			nResult = dlg.GetSelectionType();
	}
	END_TRY

	return nResult;
}

/////////////////////////////////////////////////////////////////////////////
// COleMessageFilter OLE interface implementation

BEGIN_INTERFACE_MAP(COleMessageFilter, CCmdTarget)
	INTERFACE_PART(COleMessageFilter, IID_IMessageFilter, MessageFilter)
END_INTERFACE_MAP()

STDMETHODIMP_(ULONG) COleMessageFilter::XMessageFilter::AddRef()
{
	METHOD_PROLOGUE_EX_(COleMessageFilter, MessageFilter)
	return pThis->ExternalAddRef();
}

STDMETHODIMP_(ULONG) COleMessageFilter::XMessageFilter::Release()
{
	METHOD_PROLOGUE_EX_(COleMessageFilter, MessageFilter)
	return pThis->ExternalRelease();
}

STDMETHODIMP COleMessageFilter::XMessageFilter::QueryInterface(
	REFIID iid, LPVOID* ppvObj)
{
	METHOD_PROLOGUE_EX_(COleMessageFilter, MessageFilter)
	return pThis->ExternalQueryInterface(&iid, ppvObj);
}

STDMETHODIMP_(DWORD) COleMessageFilter::XMessageFilter::HandleInComingCall(
	DWORD dwCallType, HTASK /*htaskCaller*/,
	DWORD /*dwTickCount*/, LPINTERFACEINFO /*lpInterfaceInfo*/)
{
	METHOD_PROLOGUE_EX_(COleMessageFilter, MessageFilter)

	// check for application busy first...
	if (pThis->m_nBusyCount == 0)
	{
		if (dwCallType == CALLTYPE_TOPLEVEL ||
			dwCallType == CALLTYPE_TOPLEVEL_CALLPENDING)
		{
			// make sure CWinThread::OnIdle has a chance to run later
			MSG msg;
			if (!::PeekMessage(&msg, NULL, WM_KICKIDLE, WM_KICKIDLE, PM_NOREMOVE))
				::PostThreadMessage(GetCurrentThreadId(), WM_KICKIDLE, 0, 0);
		}
		return SERVERCALL_ISHANDLED;
	}

	if (dwCallType == CALLTYPE_TOPLEVEL ||
		dwCallType == CALLTYPE_TOPLEVEL_CALLPENDING)
	{
		// application is busy and we have rejectable CALLTYPE
		return pThis->m_nBusyReply;
	}

	// application is busy, but CALLTYPE indicates that it *must* be handled
	return SERVERCALL_ISHANDLED;
}

STDMETHODIMP_(DWORD) COleMessageFilter::XMessageFilter::RetryRejectedCall(
	HTASK htaskCallee, DWORD dwTickCount, DWORD dwRejectType)
{
	METHOD_PROLOGUE_EX(COleMessageFilter, MessageFilter)
	ASSERT_VALID(pThis);

	// rejected calls get cancelled regardless of timeout
	if (dwRejectType == SERVERCALL_REJECTED)
		return (DWORD)-1;

	// if insignificant time has passed, don't panic -- just retry
	if (dwTickCount <= pThis->m_nRetryReply)
		return 0;   // retry right away (0-100 are retry immediate)

	// too much time has passed, do something more drastic
	if (pThis->m_bEnableBusy)
	{
		// show busy dialog
		int selType = pThis->OnBusyDialog(htaskCallee);

		// take action depending on selection
		switch (selType)
		{
		case -1:
			return (DWORD)-1;   // cancel outgoing call

		case COleBusyDialog::retry:
			return 0;           // retry immediately
		}
	}
	return pThis->m_nRetryReply;    // use standard retry timeout
}

STDMETHODIMP_(DWORD) COleMessageFilter::XMessageFilter::MessagePending(
	HTASK htaskCallee, DWORD dwTickCount, DWORD /*dwPendingType*/)
{
	METHOD_PROLOGUE_EX(COleMessageFilter, MessageFilter)
	ASSERT_VALID(pThis);

	MSG msg;
	if (dwTickCount > pThis->m_nTimeout && !pThis->m_bUnblocking &&
		pThis->IsSignificantMessage(&msg))
	{
		if (pThis->m_bEnableNotResponding)
		{
			pThis->m_bUnblocking = TRUE;    // avoid reentrant calls

			// eat all mouse messages in our queue
			while (PeekMessage(&msg, NULL, WM_MOUSEFIRST, WM_MOUSELAST,
				PM_REMOVE|PM_NOYIELD))
				;
			// eat all keyboard messages in our queue
			while (PeekMessage(&msg, NULL, WM_KEYFIRST, WM_KEYLAST,
				PM_REMOVE|PM_NOYIELD))
				;

			// show not responding dialog
			pThis->OnNotRespondingDialog(htaskCallee);
			pThis->m_bUnblocking = FALSE;

			return PENDINGMSG_WAITNOPROCESS;
		}
	}

	// don't process re-entrant messages
	if (pThis->m_bUnblocking)
		return PENDINGMSG_WAITDEFPROCESS;

	// allow application to process pending message
	if (::PeekMessage(&msg, NULL, NULL, NULL, PM_NOREMOVE|PM_NOYIELD) &&
		pThis->OnMessagePending(&msg))
	{
		// app has processed the message (and removed it from the queue)
		return PENDINGMSG_WAITNOPROCESS;
	}

	// by default we return pending MSG wait
	return PENDINGMSG_WAITNOPROCESS;
}

/////////////////////////////////////////////////////////////////////////////
// COleMessageFilter diagnostics

#ifdef _DEBUG
void COleMessageFilter::AssertValid() const
{
	CCmdTarget::AssertValid();
}

void COleMessageFilter::Dump(CDumpContext& dc) const
{
	CCmdTarget::Dump(dc);

	dc << "m_bRegistered = " << m_bRegistered;
	dc << "\nm_nBusyCount = " << m_nBusyCount;
	dc << "\nm_bEnableBusy = " << m_bEnableBusy;
	dc << "\nm_bEnableNotResponding = " << m_bEnableNotResponding;
	dc << "\nm_bUnblocking = " << m_bUnblocking;
	dc << "\nm_nRetryReply = " << m_nRetryReply;
	dc << "\nm_nBusyReply = " << m_nBusyReply;
	dc << "\nm_nTimeout = " << m_nTimeout;

	dc << "\n";
}
#endif

/////////////////////////////////////////////////////////////////////////////
