// XTPFlowGraphControl.cpp : implementation of the CXTPFlowGraphControl class.
//
// This file is a part of the XTREME TOOLKIT PRO MFC class library.
// (c)1998-2012 Codejock Software, All Rights Reserved.
//
// THIS SOURCE FILE IS THE PROPERTY OF CODEJOCK SOFTWARE AND IS NOT TO BE
// RE-DISTRIBUTED BY ANY MEANS WHATSOEVER WITHOUT THE EXPRESSED WRITTEN
// CONSENT OF CODEJOCK SOFTWARE.
//
// THIS SOURCE CODE CAN ONLY BE USED UNDER THE TERMS AND CONDITIONS OUTLINED
// IN THE XTREME TOOLKIT PRO LICENSE AGREEMENT. CODEJOCK SOFTWARE GRANTS TO
// YOU (ONE SOFTWARE DEVELOPER) THE LIMITED RIGHT TO USE THIS SOFTWARE ON A
// SINGLE COMPUTER.
//
// CONTACT INFORMATION:
// support@codejock.com
// http://www.codejock.com
//
/////////////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include <math.h>

#include "Common/XTPDrawHelpers.h"
#include "Common/XTPPropExchange.h"
#include "Common/XTPTooltipContext.h"
#include "Common/XTPMarkupRender.h"

#include "XTPFlowGraphElement.h"
#include "XTPFlowGraphTools.h"
#include "XTPFlowGraphControl.h"
#include "XTPFlowGraphMessages.h"
#include "XTPFlowGraphPage.h"
#include "XTPFlowGraphPages.h"
#include "XTPFlowGraphNode.h"
#include "XTPFlowGraphNodeGroup.h"
#include "XTPFlowGraphNodes.h"
#include "XTPFlowGraphPaintManager.h"
#include "XTPFlowGraphDrawContext.h"
#include "XTPFlowGraphDrawContextGdiPlus.h"
#include "XTPFlowGraphConnection.h"
#include "XTPFlowGraphConnections.h"
#include "XTPFlowGraphConnectionPoint.h"
#include "XTPFlowGraphConnectionPoints.h"
#include "XTPFlowGraphSelectedElements.h"
#include "XTPFlowGraphUndoManager.h"
#include "XTPFlowGraphEditItem.h"
#include "XTPFlowGraphImage.h"
#include "XTPFlowGraphPageHistory.h"

const TCHAR XTPFLOWGRAPH_CLASSNAME[] = _T("XTPFlowGraph");

CXTPFlowGraphControl::CXTPFlowGraphControl()
{
	CXTPFlowGraphDrawContextGdiPlus::Register(TRUE);

	m_pPaintManager = new CXTPFlowGraphPaintManager();
	m_pPaintManager->RefreshMetrics();

	m_pActivePage = NULL;
	m_pPages = new CXTPFlowGraphPages(this);

	m_dMinZoom = 0.1;
	m_dMaxZoom = 5.0;

	m_bAdjustScrollBars = FALSE;

	m_pUndoManager = new CXTPFlowGraphUndoManager();

	m_rcSelectedArea.SetRectEmpty();

	m_pToolTipContext = new CXTPToolTipContext;

	m_nAnimationDelay = 20;

	m_pImages = new CXTPFlowGraphImages();

	m_pHistory = new CXTPFlowGraphPageHistory(this);

	m_bAllowMoveNodes = TRUE;
	m_nAllowResizeNodes = xtpFlowGraphResizeHorizontal;
	m_bAllowModifyConnections = TRUE;

	m_bInAction = FALSE;

	m_nSmoothingMode = xtpFlowGraphSmoothingModeHighQuality;

	m_pMarkupContext = NULL;

	RegisterWindowClass();
}

CXTPFlowGraphControl::~CXTPFlowGraphControl()
{
	m_pUndoManager->EnableUndoManager(FALSE);

	if (m_pPaintManager)
	{
		m_pPaintManager->Cleanup();
		m_pPaintManager->InternalRelease();
		m_pPaintManager = NULL;
	}

	if (m_pImages)
	{
		m_pImages->RemoveAll();
		m_pImages->InternalRelease();
		m_pImages = NULL;
	}

	if (m_pActivePage)
	{
		m_pActivePage->InternalRelease();
		m_pActivePage = NULL;
	}

	if (m_pPages)
	{
		m_pPages->RemoveAll();
		m_pPages->InternalRelease();
		m_pPages = NULL;
	}

	if (m_pHistory)
	{
		m_pHistory->InternalRelease();
		m_pHistory = NULL;
	}

	if (m_pUndoManager)
	{
		m_pUndoManager->Clear();
		m_pUndoManager->InternalRelease();
		m_pUndoManager = NULL;
	}

	CMDTARGET_RELEASE(m_pToolTipContext);

	XTPMarkupReleaseContext(m_pMarkupContext);

	CXTPFlowGraphDrawContextGdiPlus::Register(FALSE);

}


BOOL CXTPFlowGraphControl::RegisterWindowClass(HINSTANCE hInstance /*= NULL*/)
{
	return XTPDrawHelpers()->RegisterWndClass(hInstance, XTPFLOWGRAPH_CLASSNAME, CS_DBLCLKS);
}

BOOL CXTPFlowGraphControl::Create(DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID)
{
	return CWnd::Create(XTPFLOWGRAPH_CLASSNAME, NULL, dwStyle, rect, pParentWnd, nID);
}

void CXTPFlowGraphControl::SetPaintManager(CXTPFlowGraphPaintManager* pPaintManager)
{
	if (m_pPaintManager)
	{
		m_pPaintManager->Cleanup();
		m_pPaintManager->InternalRelease();
		m_pPaintManager = NULL;
	}

	m_pPaintManager = pPaintManager;
	m_pPaintManager->RefreshMetrics();

	if (m_pActivePage)
		m_pActivePage->OnGraphChanged();
}

BEGIN_MESSAGE_MAP(CXTPFlowGraphControl, CWnd)
	ON_WM_PAINT()
	ON_WM_SIZE()
	ON_WM_HSCROLL()
	ON_WM_VSCROLL()
	ON_WM_LBUTTONDOWN()
	ON_WM_LBUTTONUP()
	ON_WM_RBUTTONUP()
	ON_WM_RBUTTONDOWN()
	ON_WM_LBUTTONDBLCLK()
	ON_WM_MBUTTONDOWN()
	ON_WM_MOUSEWHEEL()
	ON_WM_MOUSEMOVE()
END_MESSAGE_MAP()

CXTPFlowGraphDrawContext* CXTPFlowGraphControl::CreateDrawContext(CDC& dc)
{
	CXTPFlowGraphDrawContext* pDC;
	if ((m_nSmoothingMode == xtpFlowGraphSmoothingModeGDI) || (m_nSmoothingMode == xtpFlowGraphSmoothingModeAuto && GetZoomLevel() < GetPaintManager()->m_dMinZoomLevelGDIQuality))
	{
		pDC = new CXTPFlowGraphDrawContext(dc);
	}
	else
	{
		pDC = new CXTPFlowGraphDrawContextGdiPlus(dc);

		if ((m_nSmoothingMode == xtpFlowGraphSmoothingModeAuto && m_bInAction) || (m_nSmoothingMode == xtpFlowGraphSmoothingModeHighSpeed))
		{
			pDC->SetSmoothingMode(0);
		}
	}

	return pDC;
}

void CXTPFlowGraphControl::OnPaint()
{
	CPaintDC dcPaint(this);      // device context for painting
	CXTPClientRect rc(this);

	if (m_bReposition)
	{
		Reposition();
	}

	if (m_bmpCache.GetSafeHandle() == 0)
	{
		CDC memDC;
		memDC.CreateCompatibleDC(&dcPaint);

		m_bmpCache.DeleteObject();
		m_bmpCache.CreateCompatibleBitmap(&dcPaint, rc.Width(), rc.Height());

		CBitmap* pOldBitmap = memDC.SelectObject(&m_bmpCache);

		CXTPFlowGraphDrawContext* pDC = CreateDrawContext(memDC);
		pDC->SetClipRect(rc);

		OnDraw(pDC);


		dcPaint.BitBlt(0, 0, rc.right, rc.bottom, &memDC, 0, 0, SRCCOPY);

		memDC.SelectObject(pOldBitmap);

		delete pDC;
	}
	else
	{
		CDC memDC;
		memDC.CreateCompatibleDC(&dcPaint);

		CBitmap* pOldBitmap = memDC.SelectObject(&m_bmpCache);

		dcPaint.BitBlt(0, 0, rc.right, rc.bottom, &memDC, 0, 0, SRCCOPY);

		memDC.SelectObject(pOldBitmap);

	}
}

void CXTPFlowGraphControl::OnSize(UINT nType, int cx, int cy)
{
	CWnd::OnSize(nType, cx, cy);

	OnGraphChanged();
	Reposition();
	AdjustScrollBars();
}


//#define PERFOMANCE_TRACE 1

void CXTPFlowGraphControl::OnDraw(CXTPFlowGraphDrawContext* pDC)
{
#ifdef PERFOMANCE_TRACE
	_int64 nPerfomanceEnd;
	_int64 nPerfomanceStart;

	QueryPerformanceCounter((LARGE_INTEGER*)&nPerfomanceStart);
#endif

	m_pPaintManager->DrawControlBackground(pDC, this);

	if (!m_pActivePage)
		return;

	pDC->SetWorldTransform(m_pActivePage->m_ptScrollOffset, m_pActivePage->m_dZoomLevel);

	m_pActivePage->OnDraw(pDC);

	pDC->SetWorldTransform(CPoint(0, 0), 1);

	if (!m_rcSelectedArea.IsRectEmpty())
	{
		pDC->DrawSelectionRect(m_rcSelectedArea);
	}

#ifdef PERFOMANCE_TRACE
	QueryPerformanceCounter((LARGE_INTEGER*)&nPerfomanceEnd);
	TRACE(_T("CXTPFlowGraphControl::OnDraw = %i \n"), int(nPerfomanceEnd - nPerfomanceStart));
#endif
}


double CXTPFlowGraphControl::GetZoomLevel() const
{
	if (!m_pActivePage)
		return 1.0;

	return m_pActivePage->m_dZoomLevel;
}

CPoint CXTPFlowGraphControl::GetScrollOffset() const
{
	if (!m_pActivePage)
		return CPoint(0, 0);

	return m_pActivePage->GetScrollOffset();

}

void CXTPFlowGraphControl::SetZoomLevel(double dZoomLevel)
{
	if (m_pActivePage)
	{
		m_pActivePage->m_dZoomLevel = dZoomLevel;
		OnGraphChanged();
		Reposition();
		AdjustScrollBars();
	}
}

void CXTPFlowGraphControl::RedrawControl()
{
	m_bmpCache.DeleteObject();

	if (::IsWindow(m_hWnd))
	{
		Invalidate(FALSE);
	}
}

void CXTPFlowGraphControl::CancelLabelEdit()
{
	CXTPFlowGraphEditItem* pWnd = DYNAMIC_DOWNCAST(CXTPFlowGraphEditItem, CWnd::FromHandlePermanent(::GetFocus()));
	if (pWnd)
	{
		::SetFocus(m_hWnd);
	}
}

void CXTPFlowGraphControl::Reposition()
{
	if (!m_pActivePage)
		return;

	if (!m_hWnd)
		return;

	CancelLabelEdit();

	CClientDC dcClient(this);
	CXTPFlowGraphDrawContext* pDC = CreateDrawContext(dcClient);

	CRect rc;
	GetClientRect(&rc);

	m_pActivePage->Reposition(pDC, rc);

	delete pDC;

	AdjustScrollBars();

	m_bReposition = FALSE;
}

void CXTPFlowGraphControl::OnGraphChanged()
{
	m_bmpCache.DeleteObject();

	m_bReposition = TRUE;

	if (::IsWindow(m_hWnd))
	{
		Invalidate(FALSE);
	}
}

void CXTPFlowGraphControl::SetActivePage(CXTPFlowGraphPage* pPage)
{
	if (m_pActivePage == pPage)
		return;

	if (m_pActivePage)
	{
		m_pActivePage->InternalRelease();
		m_pActivePage = NULL;
	}

	m_pActivePage = pPage;

	if (m_pActivePage)
	{
		m_pActivePage->InternalAddRef();
	}

	m_pUndoManager->Clear();

	SendNotifyMessage(XTP_FGN_ACTIVEPAGECHANGED);

	OnGraphChanged();
}

void CXTPFlowGraphControl::AdjustScrollBars()
{
	if (GetSafeHwnd() == 0 || !m_pActivePage)
		return;

	if (m_bAdjustScrollBars)
		return;

	m_bAdjustScrollBars = TRUE;


	// Vertical
	{
		int nPageSize = int(m_pActivePage->m_rcPage.Height() / m_pActivePage->m_dZoomLevel);
		int nWorkTop = m_pActivePage->m_rcWorkRect.top;
		int nWorkBottom =  m_pActivePage->m_rcWorkRect.bottom;
		int nPos = m_pActivePage->m_ptScrollOffset.y;

		if (nWorkTop - nPos >= 0 && nWorkBottom - nPos <= nPageSize)
		{
			::EnableScrollBar(m_hWnd, SB_VERT, ESB_DISABLE_BOTH);
		}
		else
		{
			SCROLLINFO  si;
			si.cbSize = sizeof(SCROLLINFO);

			si.nPage = nPageSize;

			si.nMin = min(nPos, nWorkTop);
			si.nMax = max(nPos + nPageSize, nWorkBottom);

			si.fMask = SIF_PAGE | SIF_RANGE | SIF_POS;
			si.nPos = nPos;

			::EnableScrollBar(m_hWnd, SB_VERT, ESB_ENABLE_BOTH);

			SetScrollInfo(SB_VERT, &si);
			EnableScrollBarCtrl(SB_VERT, TRUE);
		}

	}

	// Horizontal
	{
		int nPageSize = int(m_pActivePage->m_rcPage.Width() / m_pActivePage->m_dZoomLevel);
		int nWorkTop = m_pActivePage->m_rcWorkRect.left;
		int nWorkBottom =  m_pActivePage->m_rcWorkRect.right;
		int nPos = m_pActivePage->m_ptScrollOffset.x;

		if (nWorkTop - nPos >= 0 && nWorkBottom - nPos <= nPageSize)
		{
			//EnableScrollBarCtrl(SB_HORZ, FALSE);
			//SetScrollPos(SB_HORZ, 0);
			::EnableScrollBar(m_hWnd, SB_HORZ, ESB_DISABLE_BOTH);

		}
		else
		{
			SCROLLINFO  si;
			si.cbSize = sizeof(SCROLLINFO);
			si.nPage = nPageSize;

			si.nMin = min(nPos, nWorkTop);
			si.nMax = max(nPos + nPageSize, nWorkBottom);

			si.fMask = SIF_PAGE | SIF_RANGE | SIF_POS;
			si.nPos = nPos;

			::EnableScrollBar(m_hWnd, SB_HORZ, ESB_ENABLE_BOTH);

			SetScrollInfo(SB_HORZ, &si);
			EnableScrollBarCtrl(SB_HORZ, TRUE);
		}
	}


	m_bAdjustScrollBars = FALSE;
}

void CXTPFlowGraphControl::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
	if (pScrollBar != NULL)
	{
		CWnd::OnHScroll(nSBCode, nPos, pScrollBar);
		return;
	}

	if (!m_pActivePage)
		return;

	int nCurPos = m_pActivePage->m_ptScrollOffset.x;

	// decide what to do for each different scroll event
	switch (nSBCode)
	{
	case SB_TOP:
		nCurPos = min(m_pActivePage->m_rcWorkRect.left, min(nCurPos, m_pActivePage->m_rcWorkRect.left));
		break;

	case SB_BOTTOM:
		nCurPos = GetScrollLimit(SB_HORZ);
		break;

	case SB_LINEUP:
		nCurPos = nCurPos - m_pActivePage->m_rcPage.Width() / 10;
		break;

	case SB_LINEDOWN:
		nCurPos = nCurPos + m_pActivePage->m_rcPage.Width() / 10;
		break;

	case SB_PAGEUP:
		nCurPos = max(nCurPos - m_pActivePage->m_rcPage.Width(), m_pActivePage->m_rcWorkRect.left);
		break;

	case SB_PAGEDOWN:
		nCurPos = min(nCurPos + m_pActivePage->m_rcPage.Width(), GetScrollLimit(SB_HORZ));
		break;

	case SB_THUMBTRACK:
	case SB_THUMBPOSITION:

		{
			SCROLLINFO si;
			ZeroMemory(&si, sizeof(SCROLLINFO));
			si.cbSize = sizeof(SCROLLINFO);
			si.fMask = SIF_TRACKPOS;

			if (!GetScrollInfo(SB_HORZ, &si))
				return;
			nCurPos = si.nTrackPos;
		}
		break;
	}

	if (nCurPos != m_pActivePage->m_ptScrollOffset.x)
	{
		m_pActivePage->m_ptScrollOffset.x = nCurPos;
		AdjustScrollBars();

		OnGraphChanged();
	}
}

void CXTPFlowGraphControl::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
	if (pScrollBar != NULL)
	{
		CWnd::OnVScroll(nSBCode, nPos, pScrollBar);
		return;
	}

	if (!m_pActivePage)
		return;

	int nCurPos = m_pActivePage->m_ptScrollOffset.y;

	// decide what to do for each different scroll event
	switch (nSBCode)
	{
	case SB_TOP:
		nCurPos = min(m_pActivePage->m_rcWorkRect.top, min(nCurPos, m_pActivePage->m_rcWorkRect.top));
		break;

	case SB_BOTTOM:
		nCurPos = GetScrollLimit(SB_VERT);
		break;

	case SB_LINEUP:
		nCurPos = nCurPos - m_pActivePage->m_rcPage.Height() / 10;
		break;

	case SB_LINEDOWN:
		nCurPos = nCurPos + m_pActivePage->m_rcPage.Height() / 10;
		break;

	case SB_PAGEUP:
		nCurPos = max(nCurPos - m_pActivePage->m_rcPage.Height(), m_pActivePage->m_rcWorkRect.top);
		break;

	case SB_PAGEDOWN:
		nCurPos = min(nCurPos + m_pActivePage->m_rcPage.Height(), GetScrollLimit(SB_VERT));
		break;

	case SB_THUMBTRACK:
	case SB_THUMBPOSITION:

		{
			SCROLLINFO si;
			ZeroMemory(&si, sizeof(SCROLLINFO));
			si.cbSize = sizeof(SCROLLINFO);
			si.fMask = SIF_TRACKPOS;

			if (!GetScrollInfo(SB_VERT, &si))
				return;
			nCurPos = si.nTrackPos;
		}
		break;
	}

	if (nCurPos != m_pActivePage->m_ptScrollOffset.y)
	{
		m_pActivePage->m_ptScrollOffset.y = nCurPos;
		AdjustScrollBars();

		OnGraphChanged();
	}
}


BOOL CXTPFlowGraphControl::OnMouseWheel(UINT /*nFlags*/, short zDelta, CPoint pt)
{
	GetCursorPos(&pt);
	ScreenToClient(&pt);

	if (!m_pActivePage)
		return FALSE;

	double dZoomLevel = m_pActivePage->m_dZoomLevel;

	int nZoom = int(m_pActivePage->m_dZoomLevel * 100);

	if (zDelta < 0)
	{
		int nLineDelta = nZoom <= 100 ? 10 : 50;

		nZoom = max(((nZoom / nLineDelta) - 1) * nLineDelta, int(100 * m_dMinZoom));
	}
	else
	{
		int nLineDelta = nZoom < 100 ? 10 : 50;

		nZoom = min(((nZoom / nLineDelta) + 1) * nLineDelta, int(100 * m_dMaxZoom));
	}

	m_pActivePage->m_dZoomLevel = nZoom / 100.0;

	if (fabs(m_pActivePage->m_dZoomLevel - dZoomLevel) < 1e-6)
		return FALSE;


	double x = pt.x / dZoomLevel + m_pActivePage->GetScrollOffset().x;
	double y = pt.y / dZoomLevel + m_pActivePage->GetScrollOffset().y;

	x = (x - pt.x / m_pActivePage->m_dZoomLevel);
	y = (y - pt.y / m_pActivePage->m_dZoomLevel);

	m_pActivePage->m_ptScrollOffset = CPoint(int(x), int(y));


	OnGraphChanged();
	Reposition();
	AdjustScrollBars();

	SendNotifyMessage(XTP_FGN_PAGEZOOMLEVELCHANGED);
	SendNotifyMessage(XTP_FGN_PAGESCROLLOFFSETCHANGED);

	return TRUE;
}

void CXTPFlowGraphControl::OnMouseMove(UINT nFlags, CPoint point)
{
	CWnd::OnMouseMove(nFlags, point);


}

CXTPFlowGraphNode* CXTPFlowGraphControl::HitTestNode(CPoint point) const
{
	if (!m_pActivePage)
		return NULL;

	for (CXTPFlowGraphNode* pNode = m_pActivePage->m_pFirstVisibleNode; pNode != NULL; pNode = pNode->m_pNextVisibleNode)
	{
		if (pNode->GetScreenRect().PtInRect(point))
		{
			return pNode;
		}
	}
	return NULL;
}

CXTPFlowGraphConnection* CXTPFlowGraphControl::HitTestConnection(CPoint point) const
{
	if (!m_pActivePage)
		return NULL;

	for (CXTPFlowGraphConnection* pConnection = m_pActivePage->m_pFirstVisibleConnection;
		pConnection != NULL; pConnection = pConnection->m_pNextVisibleConnection)
	{
		if (pConnection->PtInRect(point))
		{
			return pConnection;
		}
	}
	return NULL;
}

CXTPFlowGraphConnectionPoint* CXTPFlowGraphControl::HitTestConnectionArea(CPoint point) const
{
	if (!m_pActivePage)
		return NULL;

	CXTPFlowGraphNode* pNode = NULL;
	int i;

	for (i = m_pActivePage->GetNodes()->GetCount() - 1; i >= 0; i--)
	{
		CXTPFlowGraphNode* p = m_pActivePage->GetNodes()->GetAt(i);
		CRect rc = m_pPaintManager->GetNodeBoundingRectangle(p);
		rc = m_pActivePage->PageToScreen(rc);

		if (rc.PtInRect(point))
		{
			pNode = p;
			break;
		}
	}

	if (!pNode)
		return NULL;

	for (i = 0; i < pNode->GetConnectionPoints()->GetCount(); i++)
	{
		CXTPFlowGraphConnectionPoint* pPoint = pNode->GetConnectionPoints()->GetAt(i);

		if (pPoint->HitTestConnectionArea(point))
			return pPoint;
	}
	return NULL;
}

void CXTPFlowGraphControl::OnLButtonDown(UINT nFlags, CPoint point)
{
	CWnd::OnLButtonDown(nFlags, point);

	SetFocus();

	if (!m_pActivePage)
		return;



	CXTPFlowGraphSelectedElements* pSelection = m_pActivePage->GetSelection();

	if (m_bAllowModifyConnections)
	{
		CXTPFlowGraphConnectionPoint* pPoint = HitTestConnectionArea(point);
		if (pPoint && !pPoint->IsLocked())
		{
			StartDragConnectionPoint(pPoint);
			return;
		}
	}


	CXTPFlowGraphNode* pNode = HitTestNode(point);

	if (pNode)
	{
		if (!pNode->IsSelected())
		{
			if (!(GetKeyState(VK_CONTROL) < 0 || GetKeyState(VK_SHIFT) < 0))
				pSelection->Clear();

			pNode->Select();

			if (pSelection->GetCount() > 1)
			{

				for (int j = 0; j < m_pActivePage->GetConnections()->GetCount(); j++)
				{
					CXTPFlowGraphConnection* pConnection =  m_pActivePage->GetConnections()->GetAt(j);
					if (pConnection->GetInputPoint() && pConnection->GetInputPoint()->GetNode()->IsSelected() &&
						pConnection->GetOutputPoint() && pConnection->GetOutputPoint()->GetNode()->IsSelected() &&
						(pConnection->GetInputPoint()->GetNode() == pNode || pConnection->GetOutputPoint()->GetNode() == pNode))
					{
						pSelection->AddSelection(pConnection);
					}
				}

			}
		}
		else if (GetKeyState(VK_CONTROL) < 0)
		{
			pSelection->Remove(pNode);
		}

		m_pActivePage->GetNodes()->MoveToFront(pNode);

		if (m_nAllowResizeNodes && !pNode->IsLocked())
		{
			CRect rcNode(pNode->GetLocation(), pNode->GetSize());
			rcNode.DeflateRect(4, 4);

			rcNode = m_pActivePage->PageToScreen(rcNode);

			if (!rcNode.PtInRect(point))
			{
				StartResizeNode(pNode);
				return;
			}
		}
		StartDragNodes();
		return;
	}

	CXTPFlowGraphConnection* pConnection = HitTestConnection(point);

	if (pConnection)
	{
		if (!pConnection->IsSelected())
		{
			if (GetKeyState(VK_CONTROL) < 0 || GetKeyState(VK_SHIFT) < 0)
				pSelection->AddSelection(pConnection);
			else
				pSelection->SetSelection(pConnection);
		}
		else if (GetKeyState(VK_CONTROL) < 0)
		{
			pSelection->Remove(pConnection);
		}

		StartDragConnection(pConnection);
		return;
	}

	pSelection->Clear();
	StartDragSelection();
}

void CXTPFlowGraphControl::OnLButtonUp(UINT nFlags, CPoint point)
{
	CWnd::OnLButtonUp(nFlags, point);

	SendNotifyMessage(NM_CLICK);


}

void CXTPFlowGraphControl::OnLButtonDblClk(UINT nFlags, CPoint point)
{
	CWnd::OnLButtonDblClk(nFlags, point);

	CXTPFlowGraphConnection* pConnection = HitTestConnection(point);

	if (pConnection)
	{
		if (pConnection->GetControlPoint() != CPoint(-1, -1))
			pConnection->SetControlPoint(CPoint(-1, -1));
	}

	SendNotifyMessage(NM_DBLCLK);
}

void CXTPFlowGraphControl::OnMButtonDown(UINT nFlags, CPoint point)
{
	CWnd::OnMButtonDown(nFlags, point);

	if (!m_pActivePage)
		return;

	StartDragScreen();
}



void CXTPFlowGraphControl::OnMoveSelection(CPoint pt)
{
	CRect rc(m_ptStartDrag, pt);
	rc.NormalizeRect();

	ScreenToClient(&rc);

	m_pActivePage->GetSelection()->Clear();

	int i;

	for (i = m_pActivePage->GetNodes()->GetCount() - 1; i >= 0; i--)
	{
		CXTPFlowGraphNode* pNode = m_pActivePage->GetNodes()->GetAt(i);

		if (CRect().IntersectRect(pNode->GetScreenRect(), rc))
		{
			pNode->Select();
		}
	}

	for (i = 0; i < m_pActivePage->GetConnections()->GetCount(); i++)
	{
		CXTPFlowGraphConnection* pConnection =  m_pActivePage->GetConnections()->GetAt(i);
		if (pConnection->GetInputPoint() && pConnection->GetInputPoint()->GetNode()->IsSelected() &&
			pConnection->GetOutputPoint() && pConnection->GetOutputPoint()->GetNode()->IsSelected())
		{
			m_pActivePage->GetSelection()->AddSelection(pConnection);
		}
	}

	m_rcSelectedArea = rc;
	RedrawControl();
}

void CXTPFlowGraphControl::StartDragConnection(CXTPFlowGraphConnection* pConnection)
{
	if (!m_bAllowModifyConnections)
		return;

	// set capture to the window which received this message
	SetCapture();

	CPoint pt(0, 0);
	GetCursorPos(&pt);

	m_ptStartDrag = pt;

	m_pUndoManager->StartGroup();


	// get messages until capture lost or cancelled/accepted
	while (::GetCapture() == m_hWnd)
	{
		MSG msg;

		while (::PeekMessage(&msg, NULL, WM_PAINT, WM_PAINT, PM_NOREMOVE))
		{
			if (!GetMessage(&msg, NULL, WM_PAINT, WM_PAINT))
				break;
			DispatchMessage(&msg);
		}

		if (!::GetMessage(&msg, NULL, 0, 0))
		{
			AfxPostQuitMessage((int)msg.wParam);
			break;
		}

		if (msg.message == WM_LBUTTONUP)
			break;
		else if (msg.message == WM_MOUSEMOVE && pt != msg.pt)
		{
			pt = msg.pt;

			CPoint point(pt);
			ScreenToClient(&point);

			double x = point.x, y = point.y;
			m_pActivePage->ScreenToPage(x, y);

			pConnection->SetControlPoint(CPoint((int)x, (int)y));
		}
		else if (msg.message == WM_KEYDOWN)
		{
			if (msg.wParam == VK_ESCAPE)
			{
				break;
			}
		}
		else
			DispatchMessage(&msg);

	}

	ReleaseCapture();

	RedrawControl();

	m_pUndoManager->EndGroup();
}

void CXTPFlowGraphControl::StartDragConnectionPoint(CXTPFlowGraphConnectionPoint* pPoint)
{
	CPoint pt(0, 0);
	GetCursorPos(&pt);

	m_ptStartDrag = pt;
	BOOL bOutput = pPoint->GetType() & xtpFlowGraphPointOutput;
	CXTPFlowGraphConnectionPoint* pOutputPoint = bOutput ? pPoint : NULL;

	m_pUndoManager->StartGroup();

	if (!pOutputPoint)
	{
		int j = -1;
		for (int i = m_pActivePage->GetConnections()->GetCount() - 1; i >= 0; i--)
		{
			CXTPFlowGraphConnection* pConnection = m_pActivePage->GetConnections()->GetAt(i);

			if (pConnection->GetInputPoint() == pPoint && !pPoint->IsLocked())
			{
				if (pConnection->IsSelected())
				{
					pOutputPoint = pConnection->GetOutputPoint();
					m_pActivePage->GetConnections()->RemoveAt(i);
					break;
				}
				else if (j == -1)
				{
					j = i;
				}
			}
		}

		if (pOutputPoint == NULL && j != -1)
		{
			pOutputPoint = m_pActivePage->GetConnections()->GetAt(j)->GetOutputPoint();
			m_pActivePage->GetConnections()->RemoveAt(j);
		}

	}

	UpdateWindow();

	if (pOutputPoint == NULL)
	{
		m_pUndoManager->EndGroup();
		return;
	}

	if (pOutputPoint->GetMaxConnections() >= 0 && pOutputPoint->GetConnectionsCount() >= pOutputPoint->GetMaxConnections())
	{
		m_pUndoManager->EndGroup();
		return;
	}

	m_bInAction = TRUE;


	CXTPFlowGraphConnection* pDragConnection = new CXTPFlowGraphConnection();
	pDragConnection->SetOutputPoint(pOutputPoint);

	m_pActivePage->GetConnections()->AddConnection(pDragConnection);

	m_pActivePage->GetSelection()->Clear();
	m_pActivePage->GetSelection()->AddSelection(pDragConnection);

	ScreenToClient(&pt);
	pDragConnection->m_ptInputPoint = m_pActivePage->ScreenToPage(pt);

	CXTPFlowGraphConnectionPoint* pConnectionPoint = HitTestConnectionArea(pt);
	CXTPFlowGraphConnectionPoint* pInputConnectionPoint = NULL;

	if (pConnectionPoint && pConnectionPoint->GetType() & xtpFlowGraphPointInput &&
		pConnectionPoint->GetNode() != pDragConnection->GetOutputPoint()->GetNode())
	{
		pInputConnectionPoint = pConnectionPoint;
		m_pActivePage->GetSelection()->AddSelection(pInputConnectionPoint);
	}

	m_pActivePage->GetSelection()->AddSelection(pDragConnection->GetOutputPoint());

	// set capture to the window which received this message
	SetCapture();

	BOOL bAccept = FALSE;

	// get messages until capture lost or cancelled/accepted
	while (::GetCapture() == m_hWnd)
	{
		MSG msg;

		while (::PeekMessage(&msg, NULL, WM_PAINT, WM_PAINT, PM_NOREMOVE))
		{
			if (!GetMessage(&msg, NULL, WM_PAINT, WM_PAINT))
				break;
			DispatchMessage(&msg);
		}

		if (!::GetMessage(&msg, NULL, 0, 0))
		{
			AfxPostQuitMessage((int)msg.wParam);
			break;
		}

		if (msg.message == WM_LBUTTONUP)
		{
			bAccept = TRUE;
			break;
		}
		else if (msg.message == WM_MOUSEMOVE && pt != msg.pt)
		{
			pt = msg.pt;

			CPoint point(pt);
			ScreenToClient(&point);

			pDragConnection->m_ptInputPoint = m_pActivePage->ScreenToPage(point);
			pDragConnection->OnGraphChanged();

			CXTPFlowGraphConnectionPoint* pConnectionPoint = HitTestConnectionArea(point);

			if (pConnectionPoint && pConnectionPoint->GetType() & xtpFlowGraphPointInput && !pConnectionPoint->IsLocked() &&
				pConnectionPoint->GetNode() != pDragConnection->GetOutputPoint()->GetNode())
			{
				if ((pConnectionPoint->GetMaxConnections() == -1 || pConnectionPoint->GetConnectionsCount() < pConnectionPoint->GetMaxConnections())
					&& pInputConnectionPoint != pConnectionPoint)
				{
					XTP_NM_FLOWGRAPH_CONNECTIONCHANGING cc;
					cc.pConnection = pDragConnection;
					cc.pInputConnectionPoint = pConnectionPoint;

					if (SendNotifyMessage(XTP_FGN_CONNECTIONCHANGING, &cc.hdr) != -1)
					{
						m_pActivePage->GetSelection()->Remove(pInputConnectionPoint);

						pInputConnectionPoint = pConnectionPoint;
						m_pActivePage->GetSelection()->AddSelection(pInputConnectionPoint);
					}
				}
			}
			else
			{
				if (pInputConnectionPoint != NULL)
				{
					m_pActivePage->GetSelection()->Remove(pInputConnectionPoint);
					pInputConnectionPoint = NULL;
				}
			}


			RedrawControl();
		}
		else if (msg.message == WM_KEYDOWN)
		{
			if (msg.wParam == VK_ESCAPE)
			{
				break;
			}
		}
		else
			DispatchMessage(&msg);

	}

	ReleaseCapture();

	m_pActivePage->GetSelection()->Remove(pDragConnection->GetOutputPoint());

	if (bAccept && pInputConnectionPoint)
	{
		pDragConnection->SetInputPoint(pInputConnectionPoint);
	}

	if (pInputConnectionPoint != NULL)
	{
		m_pActivePage->GetSelection()->Remove(pInputConnectionPoint);
		pInputConnectionPoint = NULL;
	}

	if (pDragConnection->GetInputPoint() == NULL)
	{
		m_pActivePage->GetConnections()->Remove(pDragConnection);
	}

	m_pUndoManager->EndGroup();

	m_bInAction = FALSE;

	RedrawControl();
}


void CXTPFlowGraphControl::StartDragSelection()
{
	// set capture to the window which received this message
	SetCapture();

	CPoint pt(0, 0);
	GetCursorPos(&pt);

	m_ptStartDrag = pt;

	BOOL bButtonUp = FALSE;


	// get messages until capture lost or cancelled/accepted
	while (::GetCapture() == m_hWnd)
	{
		MSG msg;

		while (::PeekMessage(&msg, NULL, WM_PAINT, WM_PAINT, PM_NOREMOVE))
		{
			if (!GetMessage(&msg, NULL, WM_PAINT, WM_PAINT))
				break;
			DispatchMessage(&msg);
		}

		if (!::GetMessage(&msg, NULL, 0, 0))
		{
			AfxPostQuitMessage((int)msg.wParam);
			break;
		}

		if (msg.message == WM_LBUTTONUP)
		{
			bButtonUp = TRUE;
			break;
		}
		else if (msg.message == WM_MOUSEMOVE && pt != msg.pt)
		{
			pt = msg.pt;
			OnMoveSelection(pt);
		}
		else if (msg.message == WM_KEYDOWN)
		{
			if (msg.wParam == VK_ESCAPE)
			{
				break;
			}
		}
		else
			DispatchMessage(&msg);

	}

	ReleaseCapture();

	m_rcSelectedArea.SetRectEmpty();
	RedrawControl();

}

void CXTPFlowGraphControl::StartResizeNode(CXTPFlowGraphNode* pNode)
{
	CPoint pt(0, 0);
	GetCursorPos(&pt);

	m_ptStartDrag = pt;

	CRect rcBorders(pNode->GetLocation(), pNode->GetSize());
	rcBorders.DeflateRect(4, 4);

	rcBorders = m_pActivePage->PageToScreen(&rcBorders);
	ClientToScreen(&rcBorders);

	int ht = 0;

	if (m_nAllowResizeNodes & xtpFlowGraphResizeVertical)
	{
		if (pt.y < rcBorders.top)
			ht = (HTTOP - HTSIZEFIRST + 1);
		else if (pt.y >= rcBorders.bottom)
			ht = (HTBOTTOM - HTSIZEFIRST + 1);
	}

	if (m_nAllowResizeNodes & xtpFlowGraphResizeHorizontal)
	{
		if (pt.x < rcBorders.left)
			ht += (HTLEFT - HTSIZEFIRST + 1);
		else if (pt.x >= rcBorders.right)
			ht += (HTRIGHT - HTSIZEFIRST + 1);
	}

	if (ht == 0)
		return;

	ht = (ht + HTSIZEFIRST - 1);

	CRect rcOrigin(pNode->GetLocation(), pNode->GetSize());


	m_pUndoManager->StartGroup();

	// set capture to the window which received this message
	SetCapture();

	::SetCursor(AfxGetApp()->LoadStandardCursor(ht == HTLEFT || ht == HTRIGHT ? IDC_SIZEWE :
		ht == HTTOP || ht == HTBOTTOM ? IDC_SIZENS : ht == HTTOPLEFT || ht == HTBOTTOMRIGHT ? IDC_SIZENWSE : IDC_SIZENESW));

	// get messages until capture lost or cancelled/accepted
	while (::GetCapture() == m_hWnd)
	{
		MSG msg;

		while (::PeekMessage(&msg, NULL, WM_PAINT, WM_PAINT, PM_NOREMOVE))
		{
			if (!GetMessage(&msg, NULL, WM_PAINT, WM_PAINT))
				break;
			DispatchMessage(&msg);
		}

		if (!::GetMessage(&msg, NULL, 0, 0))
		{
			AfxPostQuitMessage((int)msg.wParam);
			break;
		}

		if (msg.message == WM_LBUTTONUP)
			break;
		else if (msg.message == WM_MOUSEMOVE && pt != msg.pt)
		{
			CPoint ptOffset(int(1 / m_pActivePage->m_dZoomLevel * (msg.pt.x - m_ptStartDrag.x)), int(1 / m_pActivePage->m_dZoomLevel * (msg.pt.y - m_ptStartDrag.y)));

			CRect rcRect(rcOrigin);
			if (ht == HTTOP || ht == HTTOPLEFT || ht == HTTOPRIGHT)
				rcRect.top += ptOffset.y;
			if (ht == HTLEFT || ht == HTTOPLEFT || ht == HTBOTTOMLEFT)
				rcRect.left += ptOffset.x;
			if (ht == HTRIGHT || ht == HTTOPRIGHT || ht == HTBOTTOMRIGHT)
				rcRect.right += ptOffset.x;
			if (ht == HTBOTTOM || ht == HTBOTTOMRIGHT || ht == HTBOTTOMLEFT)
				rcRect.bottom += ptOffset.y;

			pNode->SetLocation(rcRect.TopLeft());
			pNode->SetSize(rcRect.Size());

			UpdateWindow();

			pt = msg.pt;
		}
		else if (msg.message == WM_KEYDOWN)
		{
			if (msg.wParam == VK_ESCAPE)
			{
				break;
			}
		}
		else
			DispatchMessage(&msg);

	}

	ReleaseCapture();

	m_pUndoManager->EndGroup();
}

void CXTPFlowGraphControl::StartDragNodes()
{
	if (!m_bAllowMoveNodes)
		return;

	m_arrDragNodes.RemoveAll();

	for (int i = 0; i < m_pActivePage->GetSelection()->GetCount(); i++)
	{
		CXTPFlowGraphNode* pNode = DYNAMIC_DOWNCAST(CXTPFlowGraphNode, m_pActivePage->GetSelection()->GetAt(i));
		if (pNode)
		{
			if (!pNode->IsLocked())
			{
				DRAGNODE dn;
				dn.pElement = pNode;
				dn.bNode = TRUE;
				dn.ptOrigin = pNode->GetLocation();
				m_arrDragNodes.Add(dn);
			}
		}
		else
		{
			CXTPFlowGraphConnection* pConnection = DYNAMIC_DOWNCAST(CXTPFlowGraphConnection, m_pActivePage->GetSelection()->GetAt(i));
			if (pConnection && pConnection->GetControlPoint() != CPoint(-1, -1))
			{
				DRAGNODE dn;
				dn.pElement = pConnection;
				dn.bNode = FALSE;
				dn.ptOrigin = pConnection->GetControlPoint();
				m_arrDragNodes.Add(dn);
			}
		}
	}

	if (m_arrDragNodes.GetSize() == 0)
		return;

	m_pUndoManager->StartGroup();

	m_bInAction = TRUE;

	// set capture to the window which received this message
	SetCapture();

	CPoint pt(0, 0);
	GetCursorPos(&pt);

	m_ptStartDrag = pt;

	::SetCursor(AfxGetApp()->LoadStandardCursor(IDC_SIZEALL));

	// get messages until capture lost or cancelled/accepted
	while (::GetCapture() == m_hWnd)
	{
		MSG msg;

		while (::PeekMessage(&msg, NULL, WM_PAINT, WM_PAINT, PM_NOREMOVE))
		{
			if (!GetMessage(&msg, NULL, WM_PAINT, WM_PAINT))
				break;
			DispatchMessage(&msg);
		}

		if (!::GetMessage(&msg, NULL, 0, 0))
		{
			AfxPostQuitMessage((int)msg.wParam);
			break;
		}

		if (msg.message == WM_LBUTTONUP)
			break;
		else if (msg.message == WM_MOUSEMOVE && pt != msg.pt)
		{
			CPoint ptOffset(int(1 / m_pActivePage->m_dZoomLevel * (msg.pt.x - m_ptStartDrag.x)), int(1 / m_pActivePage->m_dZoomLevel * (msg.pt.y - m_ptStartDrag.y)));

			for (int i = 0; i < m_arrDragNodes.GetSize(); i++)
			{
				DRAGNODE& dn = m_arrDragNodes[i];

				if (dn.bNode)
				{
					((CXTPFlowGraphNode*)dn.pElement)->SetLocation(CPoint(dn.ptOrigin.x + ptOffset.x, dn.ptOrigin.y + ptOffset.y));

				}
				else
				{
					((CXTPFlowGraphConnection*)dn.pElement)->SetControlPoint(CPoint(dn.ptOrigin.x + ptOffset.x, dn.ptOrigin.y + ptOffset.y));
				}

			}
			UpdateWindow();

			pt = msg.pt;
		}
		else if (msg.message == WM_KEYDOWN)
		{
			if (msg.wParam == VK_ESCAPE)
			{
				break;
			}
		}
		else
			DispatchMessage(&msg);

	}

	ReleaseCapture();

	m_pUndoManager->EndGroup();

	m_bInAction = FALSE;
	RedrawControl();
}


void CXTPFlowGraphControl::StartDragScreen()
{
	m_bInAction = TRUE;

	// set capture to the window which received this message
	SetCapture();

	CPoint pt(0, 0);
	GetCursorPos(&pt);

	m_ptStartDrag = pt;

	CPoint ptOrigin(m_pActivePage->GetScrollOffset());


	::SetCursor(AfxGetApp()->LoadStandardCursor(IDC_SIZEALL));


	// get messages until capture lost or cancelled/accepted
	while (::GetCapture() == m_hWnd)
	{
		MSG msg;

		while (::PeekMessage(&msg, NULL, WM_PAINT, WM_PAINT, PM_NOREMOVE))
		{
			if (!GetMessage(&msg, NULL, WM_PAINT, WM_PAINT))
				break;
			DispatchMessage(&msg);
		}

		if (!::GetMessage(&msg, NULL, 0, 0))
		{
			AfxPostQuitMessage((int)msg.wParam);
			break;
		}

		if (msg.message == WM_LBUTTONUP || msg.message == WM_MBUTTONUP)
			break;
		else if (msg.message == WM_MOUSEMOVE && pt != msg.pt)
		{
			CPoint ptOffset(int(1.0 / m_pActivePage->m_dZoomLevel * (msg.pt.x - m_ptStartDrag.x)), int(1.0 / m_pActivePage->m_dZoomLevel * (msg.pt.y - m_ptStartDrag.y)));

			m_pActivePage->m_ptScrollOffset = CPoint(ptOrigin.x - ptOffset.x, ptOrigin.y - ptOffset.y);
			SendNotifyMessage(XTP_FGN_PAGESCROLLOFFSETCHANGED);

			OnGraphChanged();
			UpdateWindow();

			pt = msg.pt;
		}
		else if (msg.message == WM_KEYDOWN)
		{
			if (msg.wParam == VK_ESCAPE)
			{
				break;
			}
		}
		else
			DispatchMessage(&msg);

	}

	ReleaseCapture();

	m_bInAction = FALSE;
	RedrawControl();
}


void CXTPFlowGraphControl::DoPropExchange(CXTPPropExchange* pPX)
{
	CXTPPropExchangeSection secPages(pPX->GetSection(_T("Pages")));
	m_pPages->DoPropExchange(&secPages);

	if (pPX->IsLoading())
	{
		m_pUndoManager->Clear();
		m_pHistory->Clear();
	}
}

void CXTPFlowGraphControl::ZoomToSelection()
{
	if (!m_pActivePage)
		return;

	CRect rcWorkRect(0, 0, 0, 0);

	for (int i = 0; i < m_pActivePage->GetSelection()->GetCount(); i++)
	{
		CRect rc;

		if (CXTPFlowGraphNode* pNode = DYNAMIC_DOWNCAST(CXTPFlowGraphNode, m_pActivePage->GetSelection()->GetAt(i)))
		{
			rc = GetPaintManager()->GetNodeBoundingRectangle(pNode);
		}
		else if (CXTPFlowGraphConnection* pConnection = DYNAMIC_DOWNCAST(CXTPFlowGraphConnection, m_pActivePage->GetSelection()->GetAt(i)))
		{
			rc = pConnection->GetBoundingRect();
		}
		else
		{
			continue;
		}

		if (rcWorkRect.IsRectEmpty())
			rcWorkRect = rc;

		rcWorkRect.right = max(rcWorkRect.right, rc.right);
		rcWorkRect.bottom = max(rcWorkRect.bottom, rc.bottom);

		rcWorkRect.left = min(rcWorkRect.left, rc.left);
		rcWorkRect.top = min(rcWorkRect.top, rc.top);
	}
	if (rcWorkRect.IsRectEmpty())
		return;

	ZoomToRect(rcWorkRect);
}

void CXTPFlowGraphControl::ZoomFitToWindow()
{
	if (!m_pActivePage)
		return;

	ZoomToRect(m_pActivePage->m_rcWorkRect);
}

void CXTPFlowGraphControl::AnimatePageTransition(CXTPFlowGraphNode* pNodeFrom, CXTPFlowGraphNode* pNodeTo)
{
	CXTPFlowGraphPage* pPageFrom = pNodeFrom->GetPage();
	ASSERT (pPageFrom == m_pActivePage);

	CXTPFlowGraphPage* pPageTo = pNodeTo->GetPage();

	CPoint ptScrollOffsetToDest;
	double dZoomLevelToDest;


	if (pPageTo->m_rcWorkRect.IsRectEmpty())
	{
		CClientDC dcClient(this);
		CXTPFlowGraphDrawContext*  pDC = CreateDrawContext(dcClient);

		CRect rc;
		GetClientRect(&rc);

		pPageTo->Reposition(pDC, rc);
		GetZoomParam(pPageTo->m_rcWorkRect, ptScrollOffsetToDest, dZoomLevelToDest);

		delete pDC;
	}
	else
	{
		ptScrollOffsetToDest = pPageTo->m_ptScrollOffset;
		dZoomLevelToDest = pPageTo->m_dZoomLevel;
	}

	CRect rcNodeTo(pNodeTo->GetLocation(), pNodeTo->GetSize());

	CRect rcNodeFrom(pNodeFrom->GetLocation(), pNodeFrom->GetSize());

	CPoint ptScrollOffsetFromSrc = pPageFrom->m_ptScrollOffset;
	double dZoomLevelFromSrc = pPageFrom->m_dZoomLevel;

	CPoint ptScrollOffsetFromDest;
	double dZoomLevelFromDest;
	GetZoomParam(rcNodeFrom, ptScrollOffsetFromDest, dZoomLevelFromDest);

	CPoint ptScrollOffsetToSrc;
	double dZoomLevelToSrc;
	GetZoomParam(rcNodeTo, ptScrollOffsetToSrc, dZoomLevelToSrc);


	SetActivePage(pPageTo);

	double xFrom = ptScrollOffsetToSrc.x * dZoomLevelToSrc;
	double yFrom = ptScrollOffsetToSrc.y * dZoomLevelToSrc;

	double xTo = ptScrollOffsetToDest.x * dZoomLevelToDest;
	double yTo = ptScrollOffsetToDest.y * dZoomLevelToDest;

	const int stepCount = 15;
	int step = 0;

	UINT_PTR nIDTimer = SetTimer(1001, 5, NULL);

	CClientDC dcPaint(this);
	CXTPClientRect rc(this);

	CDC mem;
	mem.CreateCompatibleDC(&dcPaint);

	const int cx = rc.Width();
	const int cy = rc.Height();

	BITMAPINFOHEADER BMI;
	// Fill in the header info.
	ZeroMemory (&BMI, sizeof(BMI));
	BMI.biSize = sizeof(BITMAPINFOHEADER);
	BMI.biWidth = rc.Width();
	BMI.biHeight = rc.Height();
	BMI.biPlanes = 1;
	BMI.biBitCount = 32;
	BMI.biCompression = BI_RGB;   // No compression

	BYTE * pSrcBits = NULL;

	CBitmap bmpFrom;
	bmpFrom.Attach(CreateDIBSection (NULL, (BITMAPINFO *)&BMI, DIB_RGB_COLORS, (void **)&pSrcBits, 0, 0l));


	CBitmap bmpTo;
	BYTE * pDestBits = NULL;
	bmpTo.Attach(CreateDIBSection (NULL, (BITMAPINFO *)&BMI, DIB_RGB_COLORS, (void **)&pDestBits, 0, 0l));

	DWORD dwTimePer = 10;

	m_bInAction = TRUE;

	for (;;)
	{
		DWORD dwTime = GetTickCount ();

		if (step == stepCount - 1)
		{
			m_pActivePage->m_dZoomLevel = dZoomLevelToDest;
			m_pActivePage->m_ptScrollOffset = ptScrollOffsetToDest;
		}
		else
		{
			m_pActivePage->m_dZoomLevel = dZoomLevelToSrc + (dZoomLevelToDest - dZoomLevelToSrc) * (step + 1) / stepCount;

			m_pActivePage->m_ptScrollOffset =
				CPoint(int((xFrom + (xTo - xFrom) * (step + 1) / stepCount) / m_pActivePage->m_dZoomLevel),
				int((yFrom + (yTo - yFrom) * (step + 1) / stepCount) / m_pActivePage->m_dZoomLevel));
		}


		double dZoomLevelFrom = dZoomLevelFromSrc + (dZoomLevelFromDest - dZoomLevelFromSrc) * (step + 1) / stepCount;
		CPoint ptScroolOffsetFrom =
			CPoint(int((ptScrollOffsetFromSrc.x * dZoomLevelFromSrc + (ptScrollOffsetFromDest.x * dZoomLevelFromDest - ptScrollOffsetFromSrc.x * dZoomLevelFromSrc) * (step + 1) / stepCount) / dZoomLevelFrom),
			int((ptScrollOffsetFromSrc.y * dZoomLevelFromSrc + (ptScrollOffsetFromDest.y * dZoomLevelFromDest - ptScrollOffsetFromSrc.y * dZoomLevelFromSrc) * (step + 1) / stepCount) / dZoomLevelFrom));

		{
			CBitmap* pOldBitmap = mem.SelectObject(&bmpFrom);

			{
				CXTPFlowGraphDrawContext* pDC = CreateDrawContext(mem);
				pDC->SetClipRect(rc);

				m_pPaintManager->DrawControlBackground(pDC, this);

				pDC->SetWorldTransform(ptScroolOffsetFrom, dZoomLevelFrom);

				pPageFrom->OnDraw(pDC);

				pDC->SetWorldTransform(CPoint(0, 0), 1);

				delete pDC;
			}

			mem.SelectObject(pOldBitmap);

			pOldBitmap = mem.SelectObject(&bmpTo);

			{
				CXTPFlowGraphDrawContext* pDC = CreateDrawContext(mem);
				pDC->SetClipRect(rc);

				m_pPaintManager->DrawControlBackground(pDC, this);

				pDC->SetWorldTransform(m_pActivePage->m_ptScrollOffset, m_pActivePage->m_dZoomLevel);

				m_pActivePage->OnDraw(pDC);

				pDC->SetWorldTransform(CPoint(0, 0), 1);

				delete pDC;
			}

			mem.SelectObject(pOldBitmap);


			{
				int byAlpha = 255 * (step + 1) / stepCount;

				byAlpha = 255 - byAlpha;
				const BYTE byDiff = (BYTE)(255 - byAlpha);

				PBYTE pDest = pDestBits;
				PBYTE pSrc = pSrcBits;

				int cnt = cx * cy;
				for (int i = 0; i < cnt; i++)
				{
					pDest[0] = (BYTE)((pDest[0] * byDiff + pSrc[0] * byAlpha) >> 8);
					pDest[1] = (BYTE)((pDest[1] * byDiff + pSrc[1] * byAlpha) >> 8);
					pDest[2] = (BYTE)((pDest[2] * byDiff + pSrc[2] * byAlpha) >> 8);

					pDest += 4;
					pSrc += 4;
				}
			}

			pOldBitmap = mem.SelectObject(&bmpTo);

			dcPaint.BitBlt(0, 0, rc.right, rc.bottom, &mem, 0, 0, SRCCOPY);

			mem.SelectObject(pOldBitmap);
		}
		step++;

		if (step == stepCount)
			break;

		dwTime = GetTickCount () - dwTime;
		if (dwTime < dwTimePer)
		{
			Sleep(dwTimePer - dwTime);
		}
	}
	m_bInAction = FALSE;

	AdjustScrollBars();
	SendNotifyMessage(XTP_FGN_PAGESCROLLOFFSETCHANGED);
	SendNotifyMessage(XTP_FGN_PAGEZOOMLEVELCHANGED);

	KillTimer(nIDTimer);

	RedrawControl();

}

void CXTPFlowGraphControl::AnimateTo(CPoint ptScrollOffset, double dZoomLevel)
{
	if (!m_pActivePage)
		return;

	CPoint ptOldScrollOffset = m_pActivePage->m_ptScrollOffset;
	double dOldZoomLevel = m_pActivePage->m_dZoomLevel;

	if (ptOldScrollOffset == ptScrollOffset && fabs(dOldZoomLevel - dZoomLevel) < 1e-6)
		return;

	double xFrom = ptOldScrollOffset.x * dOldZoomLevel;
	double yFrom = ptOldScrollOffset.y * dOldZoomLevel;

	double xTo = ptScrollOffset.x * dZoomLevel;
	double yTo = ptScrollOffset.y * dZoomLevel;

	const int stepCount = 15;
	int step = 0;

	int dwStart = (int)GetTickCount();

	UINT_PTR nIDTimer = SetTimer(1001, m_nAnimationDelay, NULL);

	m_bInAction = TRUE;

	for (;;)
	{
		if ((int)GetTickCount() - dwStart > m_nAnimationDelay)
		{
			dwStart = (int)GetTickCount();

			if (step == stepCount - 1)
			{
				m_pActivePage->m_dZoomLevel = dZoomLevel;
				m_pActivePage->m_ptScrollOffset = ptScrollOffset;
			}
			else
			{
				m_pActivePage->m_dZoomLevel = dOldZoomLevel + (dZoomLevel - dOldZoomLevel) * (step + 1) / stepCount;

				m_pActivePage->m_ptScrollOffset =
					CPoint(int((xFrom + (xTo - xFrom) * (step + 1) / stepCount) / m_pActivePage->m_dZoomLevel),
					int((yFrom + (yTo - yFrom) * (step + 1) / stepCount) / m_pActivePage->m_dZoomLevel));
			}

			RedrawControl();
			AdjustScrollBars();
			UpdateWindow();
			step++;

			if (step == stepCount)
				break;
		}


		MSG msg;

		while (::PeekMessage(&msg, NULL, WM_PAINT, WM_PAINT, PM_NOREMOVE))
		{
			if (!GetMessage(&msg, NULL, WM_PAINT, WM_PAINT))
				break;
			DispatchMessage(&msg);
		}

		if (!::GetMessage(&msg, NULL, 0, 0))
		{
			AfxPostQuitMessage((int)msg.wParam);
			break;
		}

		DispatchMessage (&msg);
	}

	m_bInAction = FALSE;

	SendNotifyMessage(XTP_FGN_PAGESCROLLOFFSETCHANGED);
	SendNotifyMessage(XTP_FGN_PAGEZOOMLEVELCHANGED);

	KillTimer(nIDTimer);

	RedrawControl();

}

void CXTPFlowGraphControl::GetZoomParam(LPCRECT lpRect, CPoint& ptScrollOffset, double& dZoomLevel)
{
	CRect rc(lpRect);

	CRect rcClient;
	GetClientRect(&rcClient);

	dZoomLevel = 0;
	ptScrollOffset = rc.TopLeft();

	if (rc.Height() * rcClient.Width() > rcClient.Height() * rc.Width())
	{
		dZoomLevel = (double)rcClient.Height() / rc.Height();

		int nWidth = int(rcClient.Width() / dZoomLevel);

		ptScrollOffset.x -= (nWidth - rc.Width()) / 2;
	}
	else
	{
		dZoomLevel = (double)rcClient.Width() / rc.Width();

		int nHeight = int(rcClient.Height() / dZoomLevel);

		ptScrollOffset.y -= (nHeight - rc.Height()) / 2;

	}
}

void CXTPFlowGraphControl::ZoomToRect(LPCRECT lpRect, BOOL bAnimate)
{
	if (!m_pActivePage || !GetSafeHwnd())
		return;

	CPoint ptScrollOffset;
	double dZoomLevel;

	GetZoomParam(lpRect, ptScrollOffset, dZoomLevel);

	if (bAnimate)
	{
		AnimateTo(ptScrollOffset, dZoomLevel);
	}
	else
	{
		m_pActivePage->m_dZoomLevel = dZoomLevel;
		m_pActivePage->m_ptScrollOffset = ptScrollOffset;

		SendNotifyMessage(XTP_FGN_PAGESCROLLOFFSETCHANGED);
		SendNotifyMessage(XTP_FGN_PAGEZOOMLEVELCHANGED);

		OnGraphChanged();
		Reposition();
		AdjustScrollBars();
	}
}

void CXTPFlowGraphControl::SelectAll()
{
	if (!m_pActivePage)
		return;
	m_pActivePage->GetSelection()->Clear();

	int i;

	for (i = 0; i < m_pActivePage->GetNodes()->GetCount(); i++)
		m_pActivePage->GetSelection()->AddSelection(m_pActivePage->GetNodes()->GetAt(i));

	for (i = 0; i < m_pActivePage->GetConnections()->GetCount(); i++)
		m_pActivePage->GetSelection()->AddSelection(m_pActivePage->GetConnections()->GetAt(i));

	RedrawControl();
}

void CXTPFlowGraphControl::Arrange()
{
	if (!m_pActivePage)
		return;

	m_pActivePage->Arrange();
}

INT_PTR CXTPFlowGraphControl::OnToolHitTest(CPoint point, TOOLINFO* pTI) const
{
	ASSERT_VALID(this);
	ASSERT(::IsWindow(m_hWnd));

	// check child windows first by calling CControlBar
	INT_PTR nHit = CWnd::OnToolHitTest(point, pTI);
	if (nHit != -1)
		return nHit;

	CXTPFlowGraphNode* pNode = HitTestNode(point);

	if (pNode)
	{
		CXTPFlowGraphConnectionPoint* pConnectionPoint = pNode->HitTestConnectionPoint(point);

		if (pConnectionPoint && !pConnectionPoint->GetTooltip().IsEmpty())
		{
			nHit = (INT_PTR)pConnectionPoint;

			CString strTip = pConnectionPoint->GetTooltip();
			if (strTip.GetLength() == 0)
				return -1;

			CXTPToolTipContext::FillInToolInfo(pTI, m_hWnd, pNode->GetScreenRect(), nHit, strTip);

			return nHit;
		}
		else
		{
			nHit = (INT_PTR)pNode;

			CString strTip = pNode->GetTooltip();
			if (strTip.GetLength() == 0)
				return -1;

			CXTPToolTipContext::FillInToolInfo(pTI, m_hWnd, pNode->GetScreenRect(), nHit, strTip);

			return nHit;
		}
	}
	CXTPFlowGraphConnection* pConnection = HitTestConnection(point);

	if (pConnection)
	{
		nHit = (INT_PTR)pConnection;

		CString strTip = pConnection->GetTooltip();
		if (strTip.GetLength() == 0)
			return -1;

		CXTPToolTipContext::FillInToolInfo(pTI, m_hWnd,
			CRect(point.x - 3, point.y - 3, point.x + 3, point.y + 3), nHit, strTip);

		return nHit;
	}


	return -1;
}



void CXTPFlowGraphControl::RenameNode(CXTPFlowGraphNode* pNode)
{
	if (!pNode)
		return;

	CXTPFlowGraphEditItem* pEditItem = new CXTPFlowGraphEditItem;
	pEditItem->Create(pNode);
}

void CXTPFlowGraphControl::RenameConnectionPoint(CXTPFlowGraphConnectionPoint* pConnectionPoint)
{
	if (!pConnectionPoint)
		return;

	CXTPFlowGraphEditItem* pEditItem = new CXTPFlowGraphEditItem;
	pEditItem->Create(pConnectionPoint);
}

void CXTPFlowGraphControl::SetSmoothingMode(XTPFlowGraphSmoothingMode nSmoothingMode)
{
	m_nSmoothingMode = nSmoothingMode;

	if (m_pActivePage) m_pActivePage->OnGraphChanged();
	RedrawControl();
}
void CXTPFlowGraphControl::OnEndLabelEdit(CXTPFlowGraphElement* pItem, LPCTSTR str)
{
	XTP_NM_FLOWGRAPH_ENDLABELEDIT ele;
	ele.pItem = pItem;
	ele.strNewString = str;

	if (SendNotifyMessage(XTP_FGN_ENDLABELEDIT, &ele.hdr) == -1)
		return;


	pItem->SetCaption(ele.strNewString);
}


LRESULT CXTPFlowGraphControl::SendNotifyMessage(UINT nMessage, NMHDR* pNMHDR) const
{
	if (!IsWindow(m_hWnd))
		return 0;

	NMHDR nmhdr;
	if (pNMHDR == NULL)
		pNMHDR = &nmhdr;

	pNMHDR->hwndFrom = GetSafeHwnd();
	pNMHDR->idFrom = GetDlgCtrlID();
	pNMHDR->code = nMessage;

	CWnd *pOwner = GetOwner();
	if (pOwner && IsWindow(pOwner->m_hWnd))
		return pOwner->SendMessage(WM_NOTIFY, pNMHDR->idFrom, (LPARAM)pNMHDR);
	else
		return 0;
}


void CXTPFlowGraphControl::OnRButtonDown(UINT nFlags, CPoint point)
{
	CWnd::OnRButtonDown(nFlags, point);

	SetFocus();


}

void CXTPFlowGraphControl::OnRButtonUp(UINT nFlags, CPoint point)
{
	CWnd::OnRButtonUp(nFlags, point);

	SendNotifyMessage(NM_RCLICK);

}


BOOL CXTPFlowGraphControl::OnWndMsg(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
	if (m_pToolTipContext)
	{
		m_pToolTipContext->FilterToolTipMessage(this, message, wParam, lParam);
	}

	return CWnd::OnWndMsg(message, wParam, lParam, pResult);
}

void CXTPFlowGraphControl::EnableMarkup(BOOL bEnable)
{
	XTPMarkupReleaseContext(m_pMarkupContext);

	if (bEnable)
	{
		m_pMarkupContext = XTPMarkupCreateContext();
	}
}