// IDropSourceImpl.h: interfejs dla klasy IDropSourceImpl.
//
//////////////////////////////////////////////////////////////////////

#if !defined(AFX_IDROPSOURCEIMPL_H__6ED143B2_4A64_11D2_BCF0_00A0C9C8E50D__INCLUDED_)
#define AFX_IDROPSOURCEIMPL_H__6ED143B2_4A64_11D2_BCF0_00A0C9C8E50D__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

template <class T>
class ATL_NO_VTABLE IDropSourceImpl : public IDropSource  
{
public:
    IDropSourceImpl()
    {
	    m_bDragStarted = false;
	    m_dwButtonCancel = 0;
	    m_dwButtonDrop = 0;
    }
    virtual ~IDropSourceImpl() {}

	static void WINAPI ObjectMain(bool /* bStarting */)
    {
        // pobranie informacji z pliku win.ini
        static const TCHAR szWindows[] = _T("windows");
        static const TCHAR szDragMinDist[] = _T("DragMinDist");
        static const TCHAR szDragDelay[] = _T("DragDelay");

        nDragMinDist = ::GetProfileInt(szWindows, szDragMinDist, DD_DEFDRAGMINDIST);
        nDragDelay = ::GetProfileInt(szWindows, szDragDelay, DD_DEFDRAGDELAY);
    }

    DWORD DoDragDrop(DWORD dwEffects, LPCRECT lpRectStartDrag)
    {
        ATLTRACE2(atlTraceCOM, 0, _T("IDropSourceImpl::DoDragDrop\n"));
        T* pT = static_cast<T*>(this);

        // ustawienie prostokąta i jego wrażliwości na przeciąganie
	    m_bDragStarted = false;

        if (lpRectStartDrag != NULL) {
		    // ustawienie prostokąta na podane parametry
		    m_rectStartDrag.CopyRect(lpRectStartDrag);
	    }
	    else {
		    // w przeciwnym wypadku użycie domyślnego, pustego prostokąta wokół aktualnego punktu
		    CPoint ptCursor;
		    GetCursorPos(&ptCursor);
		    m_rectStartDrag.SetRect(
			    ptCursor.x, ptCursor.y, ptCursor.x, ptCursor.y);
	    }

	    if (m_rectStartDrag.IsRectNull()) {
		    // pusty prostokąt decyduje o braku pętli OnBeginDrag
		    m_bDragStarted = true;
	    }
	    else if (m_rectStartDrag.IsRectEmpty()) {
		    // pusty prostokąt decyduje o obszarze wokół punktu startowego
		    m_rectStartDrag.InflateRect(nDragMinDist, nDragMinDist);
	    }

	    // przed wywołaniem kodu OLE należy poczekać na przeniesienie myszy
	    //  poza obszar prostokąta
	    if (!OnBeginDrag(pT))
		    return DROPEFFECT_NONE;

	    // wywołanie globalnego API OLE
	    CComPtr<IDataObject> spDataObject ;
        HRESULT hr = pT->_InternalQueryInterface (IID_IDataObject, (void**) &spDataObject);
        ATLASSERT (SUCCEEDED (hr));

	    CComPtr<IDropSource> spDropSource ;
        hr = pT->_InternalQueryInterface (IID_IDropSource, (void**) &spDropSource);
        ATLASSERT (SUCCEEDED (hr));

	    DWORD dwResultEffect = DROPEFFECT_NONE;
	    ::DoDragDrop(spDataObject, spDropSource, dwEffects, &dwResultEffect);
	    return dwResultEffect;
    }

// Implementacja
public:
// IDropSource
    STDMETHOD(QueryContinueDrag)(BOOL fEscapePressed, DWORD grfKeyState)
    {
        ATLTRACE2(atlTraceCOM, 0, _T("IDropSourceImpl::QueryContinueDrag\n"));
        T* pT = static_cast<T*>(this);

	    // sprawdzenie przycisku Escape lub prawego przycisku myszy i anulowanie
	    if (fEscapePressed || (grfKeyState & m_dwButtonCancel) != 0) {
		    m_bDragStarted = false; // uniknięcie niepotrzebnych ustawień wskaźnika
		    return DRAGDROP_S_CANCEL;
	    }

	    // sprawdzenie lewego i prawego przycisku myszy w celu zakończenia przeciągania
	    if ((grfKeyState & m_dwButtonDrop) == 0)
		    return m_bDragStarted ? DRAGDROP_S_DROP : DRAGDROP_S_CANCEL;

	    // w przeciwnym wypadku ciągła kontrola...
	    return S_OK;
    }

    STDMETHOD(GiveFeedback)(DWORD)
    {
        ATLTRACE2(atlTraceCOM, 0, _T("IDropSourceImpl::GiveFeedback\n"));
        T* pT = static_cast<T*>(this);
        // nie należy zmieniać kursora aż do momentu rozpoczęcia przeciągania
        return m_bDragStarted ? DRAGDROP_S_USEDEFAULTCURSORS : S_OK;
    }

    virtual bool OnBeginDrag(CWindow* pWnd)
    {
        ATLTRACE2(atlTraceCOM, 0, _T("IDropSourceImpl::OnBeginDrag\n"));
        T* pT = static_cast<T*>(this);

	    m_bDragStarted = false;

	    // drugi przycisk przerywa operację przeciągania
	    m_dwButtonCancel = 0;
	    m_dwButtonDrop = 0;
	    if (GetKeyState(VK_LBUTTON) < 0) {
		    m_dwButtonDrop |= MK_LBUTTON;
		    m_dwButtonCancel |= MK_RBUTTON;
	    }
	    else if (GetKeyState(VK_RBUTTON) < 0) {
		    m_dwButtonDrop |= MK_RBUTTON;
		    m_dwButtonCancel |= MK_LBUTTON;
	    }

	    DWORD dwLastTick = GetTickCount();
        if (pT->m_bWndLess) {
	        HRESULT hr = pT->m_spInPlaceSite->SetCapture(TRUE);
        }
        else
            pWnd->SetCapture();

	    while (!m_bDragStarted) {
		    if (pT->m_bWndLess) {
    	        if (S_FALSE == pT->m_spInPlaceSite->GetCapture())
                    break ;
            }
            else
                if (::GetCapture() != pWnd->m_hWnd)
			        break;

		    // sprawdzenie kolejnej wiadomości
		    MSG msg;
		    if (PeekMessage(&msg, NULL, WM_MOUSEFIRST, WM_MOUSELAST, PM_REMOVE) ||
			    PeekMessage(&msg, NULL, WM_KEYFIRST, WM_KEYLAST, PM_REMOVE)) {
			    // sprawdzenie anulowania (dowolny przycisk)
			    if (msg.message == WM_LBUTTONUP || msg.message == WM_RBUTTONUP ||
				    msg.message == WM_LBUTTONDOWN || msg.message == WM_RBUTTONDOWN)
				    break;

			    // sprawdzenie anulowania z klawiatury
			    if (msg.message == WM_KEYDOWN && msg.wParam == VK_ESCAPE)
				    break;

			    // sprawdzenie początku przeciągania
			    m_bDragStarted = !m_rectStartDrag.PtInRect(msg.pt);
		    }

		    // początek przeciągania po upływie określonego czasu
		    if (GetTickCount() - dwLastTick > nDragDelay)
			    m_bDragStarted = true;
	    }
        if (pT->m_bWndLess) {
	        HRESULT hr = pT->m_spInPlaceSite->SetCapture(FALSE);
        }
        else
            ReleaseCapture();

	    return m_bDragStarted;
    }

    CRect m_rectStartDrag;  // przeciąganie rozpoczyna się po opuszczeniu tego prostokąta przez mysz
	bool m_bDragStarted;    // czy przeciąganie naprawdę się zaczęło?
	DWORD m_dwButtonCancel; // który klawisz spowoduje przerwanie (wciśnięcie)
	DWORD m_dwButtonDrop;   // który klawisz spowoduje potwierdzenie (zwolnienie)

	// ustawienia służące do ustalenia początku przeciągania
	static UINT nDragMinDist;  // minimalny dystans przeciągania
	static UINT nDragDelay;    // opóźnienie przed początkiem przeciągania

};

template <class T> UINT IDropSourceImpl<T>::nDragMinDist;
template <class T> UINT IDropSourceImpl<T>::nDragDelay;


#endif // !defined(AFX_IDROPSOURCEIMPL_H__6ED143B2_4A64_11D2_BCF0_00A0C9C8E50D__INCLUDED_)
