//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Implements the angle custom control, a circle with a line indicating
//			a rotation angle.
//
//=============================================================================//

#include "stdafx.h"
#include "hammer.h"
#include "AngleBox.h"
#include "hammer_mathlib.h"
#include "CustomMessages.h"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

#pragma warning(disable: 4244)


BEGIN_MESSAGE_MAP(CAngleBox, CWnd)
	//{{AFX_MSG_MAP(CAngleBox)
	ON_WM_MOUSEMOVE()
	ON_WM_LBUTTONUP()
	ON_WM_LBUTTONDOWN()
	ON_WM_PAINT()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()


//-----------------------------------------------------------------------------
// Purpose: Constructor.
//-----------------------------------------------------------------------------
CAngleBox::CAngleBox(void)
{
	m_vecAngles.Init();
	m_bDragging = false;
	m_pEdit = NULL;
}


//-----------------------------------------------------------------------------
// Purpose: Destructor.
//-----------------------------------------------------------------------------
CAngleBox::~CAngleBox()
{
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : nFlags - 
//			point - 
//-----------------------------------------------------------------------------
void CAngleBox::OnMouseMove(UINT nFlags, CPoint point) 
{
	if (m_bDragging)
	{
		//
		// Remove old angle line by redrawing it (XOR).
		//
		DrawAngleLine(&m_DragDC);

		//
		// Calculate new yaw.
		//
		int nNewYaw = fixang(180 - (int)lineangle(point.x, point.y, m_ptClientCenter.x, m_ptClientCenter.y));
		m_vecAngles.Init();
		m_vecAngles[YAW] = nNewYaw;
		
		//
		// Draw the new angle line.
		//
		DrawAngleLine(&m_DragDC);
	}

	CWnd::OnMouseMove(nFlags, point);
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : nFlags - 
//			point - 
//-----------------------------------------------------------------------------
void CAngleBox::OnLButtonUp(UINT nFlags, CPoint point) 
{
	// release dc
	if (m_bDragging)
	{
		::ReleaseDC(m_hWnd, m_DragDC.Detach());
		m_bDragging = false;
		ReleaseCapture();

		//
		// They've explicity set the angles, so clear the different flag for
		// the multiselect case.
		//
		SetDifferent(false);

		GetParent()->PostMessage(ABN_CHANGED, GetDlgCtrlID(), 0);
	}
		
	CWnd::OnLButtonUp(nFlags, point);
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : nFlags - 
//			point - 
//-----------------------------------------------------------------------------
void CAngleBox::OnLButtonDown(UINT nFlags, CPoint point) 
{
	//
	// Start dragging.
	//
	m_DragDC.Attach(::GetDC(m_hWnd));
	m_bDragging = true;
	SetCapture();

	CWnd::OnLButtonDown(nFlags, point);

	OnMouseMove(0, point);
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : pDC - 
//-----------------------------------------------------------------------------
void CAngleBox::DrawAngleLine(CDC *pDC)
{
	if ((m_vecAngles[PITCH] != 0) || (m_vecAngles[ROLL] != 0) ||
		(m_vecAngles[YAW] < 0 || m_vecAngles[YAW] > 359) ||	m_bDifferent)
	{
		return;
	}

	pDC->SetROP2(R2_XORPEN);
	pDC->SelectStockObject(WHITE_PEN);

	CRect r;
	GetClientRect(r);
	m_ptClientCenter = r.CenterPoint();

	double rad = r.Width() / 2 - 3;

	CPoint pt;
	pt.x = m_ptClientCenter.x + sin(DEG2RAD((double)(m_vecAngles[YAW] + 90))) * rad + 0.5;
	pt.y = m_ptClientCenter.y + cos(DEG2RAD((double)(m_vecAngles[YAW] + 90))) * rad + 0.5;

	pDC->MoveTo(m_ptClientCenter);
	pDC->LineTo(pt);
}


//-----------------------------------------------------------------------------
// Purpose: Returns the current state of the control as a keyvalue string.
// Input  : szAngles - Buffer to receive angles string.
// Output : Returns 'szAngles'.
//-----------------------------------------------------------------------------
bool CAngleBox::GetAngles(QAngle &vecAngles)
{
	if (m_bDifferent)
	{
		return false;
	}

	vecAngles = m_vecAngles;

	return(true);
}


//-----------------------------------------------------------------------------
// Purpose: Returns the current state of the control as a keyvalue string.
// Input  : szAngles - Buffer to receive angles string.
// Output : Returns 'szAngles'.
//-----------------------------------------------------------------------------
char *CAngleBox::GetAngles(char *szAngles)
{
	QAngle vecAngles;
	GetAngles(vecAngles);
	sprintf(szAngles, "%g %g %g", (double)vecAngles[0], (double)vecAngles[1], (double)vecAngles[2]);
	return(szAngles);
}


//-----------------------------------------------------------------------------
// Purpose: Returns a string indicating the current state of the angle control.
//			This is used for setting the text in the companion edit control.
// Input  : szBuf - Buffer to receive string.
//-----------------------------------------------------------------------------
char *CAngleBox::GetAngleEditText(char *szBuf)
{
	szBuf[0] = '\0';

	if (m_bDifferent)
	{
		strcpy(szBuf, "(diff)");
	}
	else if ((m_vecAngles[PITCH] == 90) && (m_vecAngles[YAW] == 0) && (m_vecAngles[ROLL] == 0))
	{
		strcpy(szBuf, "Down");
	}
	else if ((m_vecAngles[PITCH] == -90) && (m_vecAngles[YAW] == 0) && (m_vecAngles[ROLL] == 0))
	{
		strcpy(szBuf, "Up");
	}
	else if (m_vecAngles[YAW] >= 0)
	{
		itoa((int)m_vecAngles[YAW], szBuf, 10);
	}

	return(szBuf);
}


//-----------------------------------------------------------------------------
// Purpose: Called internally and by the linked combo box, this updates the angles
//			without updating the linked combo box.
// Input  : szAngles - 
//			bRedraw - 
//-----------------------------------------------------------------------------
void CAngleBox::SetAnglesInternal(const QAngle &vecAngles, bool bRedraw)
{
	QAngle vecAngleSet = vecAngles;
	while (vecAngleSet[YAW] < 0)
	{
		vecAngleSet[YAW] += 360.0;
	}

	CDC *pDC = NULL;

	if (bRedraw)
	{
		//
		// Erase the old line.
		//
		Assert(::IsWindow(m_hWnd));
		pDC = GetDC();
		if (pDC != NULL)
		{
			DrawAngleLine(pDC);
		}
	}

	//
	// Update the data member.
	//
	m_vecAngles = vecAngleSet;

	if ((bRedraw) && (pDC != NULL))
	{
		//
		// Draw the new line.
		//
		DrawAngleLine(pDC);
		ReleaseDC(pDC);
	}
}


//-----------------------------------------------------------------------------
// Purpose: Called from the client code, this sets our angles and updates the
//			linked combo box.
// Input  : szAngles - 
//			bRedraw - 
//-----------------------------------------------------------------------------
void CAngleBox::SetAngles(const QAngle &vecAngles, bool bRedraw)
{
	SetAnglesInternal(vecAngles, bRedraw);
	UpdateAngleEditText();
}


//-----------------------------------------------------------------------------
// Purpose: Called from the client code, this sets our angles via a string and
//			updates the linked combo box.
// Input  : szAngles - 
//			bRedraw - 
//-----------------------------------------------------------------------------
void CAngleBox::SetAngles(const char *szAngles, bool bRedraw)
{
	QAngle vecAngles(0, 0, 0);
	sscanf(szAngles, "%f %f %f", &vecAngles[PITCH], &vecAngles[YAW], &vecAngles[ROLL]);
	SetAngles(vecAngles, bRedraw);
}


//-----------------------------------------------------------------------------
// Purpose: Called internally and by the linked combo box, this sets our
//			'different' state without updating the linked combo box.
// Input  : bDifferent - 
//			bRedraw - 
//-----------------------------------------------------------------------------
void CAngleBox::SetDifferentInternal(bool bDifferent, bool bRedraw)
{
	CDC *pDC = NULL;

	if (bRedraw)
	{
		//
		// Erase the old line.
		//
		Assert(::IsWindow(m_hWnd));
		pDC = GetDC();
		if (pDC != NULL)
		{
			DrawAngleLine(pDC);
		}
	}

	//
	// Update the data member.
	//
	m_bDifferent = bDifferent;

	if ((bRedraw) && (pDC != NULL))
	{
		//
		// Draw the new line.
		//
		DrawAngleLine(pDC);
		ReleaseDC(pDC);
	}
}


//-----------------------------------------------------------------------------
// Purpose: Sets our state to indicate multiselect of objects with different
//			angles to avoid mucking with the angles unless they explicitly set
//			them to something new.
//-----------------------------------------------------------------------------
void CAngleBox::SetDifferent(bool bDifferent, bool bRedraw)
{
	SetDifferentInternal(bDifferent, bRedraw);
	UpdateAngleEditText();
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CAngleBox::OnPaint(void)
{
	PAINTSTRUCT ps;
	CDC *pDC = BeginPaint(&ps);

	if (pDC == NULL)
	{
		return;
	}

	CBrush brushWindow(GetSysColor(COLOR_3DFACE));
	CBrush brushBlack(RGB(0, 0, 0));

	CBrush *pBackBrush = IsWindowEnabled() ? &brushBlack : &brushWindow;

	CRect r;
	GetClientRect(r);

	//
	// Fill with the window color.
	//
	pDC->FillRect(&r, &brushWindow);

	//
	// Draw a 3D circle.
	//
	m_ptClientCenter = r.CenterPoint();
	
	pDC->SelectStockObject(NULL_PEN);
	pDC->SelectObject(pBackBrush);
	pDC->Ellipse(r);

	CPen hi(PS_SOLID, 2, GetSysColor(COLOR_3DSHADOW));
	CPen lo(PS_SOLID, 2, GetSysColor(COLOR_3DHILIGHT));

	pDC->SelectObject(hi);
	pDC->Arc(r, CPoint(r.right, r.top), CPoint(r.left, r.bottom));
	pDC->SelectObject(lo);
	pDC->Arc(r, CPoint(r.left, r.bottom), CPoint(r.right, r.top));

	//
	// Draw center point.
	//
	pDC->SetPixel(m_ptClientCenter, RGB(0xff, 0xff, 0xff));

	//
	// Draw line indicating angles direction.
	//
	if (IsWindowEnabled())
	{
		DrawAngleLine(pDC);
	}

	EndPaint(&ps);
}


//-----------------------------------------------------------------------------
// Purpose: Enables or disables the angles controls.
//-----------------------------------------------------------------------------
void CAngleBox::Enable(bool bEnable)
{
	if (bEnable)
	{
		EnableWindow(TRUE);

		if (m_pEdit)
		{
			m_pEdit->EnableWindow(TRUE);
		}
	}
	else
	{
		EnableWindow(FALSE);
		
		if (m_pEdit)
		{
			m_pEdit->EnableWindow(FALSE);
		}
	}

	Invalidate(FALSE);
	UpdateWindow();
}


//-----------------------------------------------------------------------------
// Purpose: Hides or shows the angles controls.
//-----------------------------------------------------------------------------
void CAngleBox::Show(bool bShow)
{
	if (bShow)
	{
		ShowWindow(SW_SHOW);
		
		if (m_pEdit)
		{
			m_pEdit->ShowWindow(SW_SHOW);
		}
	}
	else
	{
		ShowWindow(SW_HIDE);

		if (m_pEdit)
		{
			m_pEdit->ShowWindow(SW_HIDE);
		}
	}

	Invalidate(FALSE);
	UpdateWindow();
}


//-----------------------------------------------------------------------------
// Purpose: Updates the text in the angle combo to reflect the current angles
//			in the angles control.
//-----------------------------------------------------------------------------
void CAngleBox::UpdateAngleEditText(void)
{
	if (m_pEdit)
	{
		char szBuf[20];
		GetAngleEditText(szBuf);
		m_pEdit->SetAnglesInternal(szBuf);
	}
}


BEGIN_MESSAGE_MAP(CAngleCombo, CWnd)
	//{{AFX_MSG_MAP(CAngleBox)
	ON_CONTROL_REFLECT(CBN_EDITCHANGE, OnChangeAngleEdit)
	ON_CONTROL_REFLECT(CBN_SELENDOK, OnSelChangeAngleEdit)
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()


//-----------------------------------------------------------------------------
// Purpose: Construktor.
//-----------------------------------------------------------------------------
CAngleCombo::CAngleCombo()
	: CComboBox()
{
	m_pBox = NULL;
	m_bEnableUpdate = true;
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *szAngles - 
//-----------------------------------------------------------------------------
void CAngleCombo::SetAnglesInternal(const char *szAngles)
{
	m_bEnableUpdate = false;
	SetWindowText(szAngles);
	m_bEnableUpdate = true;
}


//-----------------------------------------------------------------------------
// Purpose: Handles a change in the contents of the angle edit control.
//-----------------------------------------------------------------------------
void CAngleCombo::OnChangeAngleEdit(void) 
{
	if (m_bEnableUpdate)
	{
		char buf[64];
		GetWindowText(buf, 64);
		UpdateAngleBox(buf);

		GetParent()->PostMessage(ABN_CHANGED, GetDlgCtrlID(), 0);
	}
}


//-----------------------------------------------------------------------------
// Purpose: Handles a change in the current selection of the angle edit combo.
//-----------------------------------------------------------------------------
void CAngleCombo::OnSelChangeAngleEdit(void) 
{
	char buf[64];
	int nSel = GetCurSel();
	GetLBText(nSel, buf);
	UpdateAngleBox(buf);

	GetParent()->PostMessage(ABN_CHANGED, GetDlgCtrlID(), 0);
}


//-----------------------------------------------------------------------------
// Purpose: Updates angle box with the settings from the combo box. Call the
//			internal functions so we don't get a reflected notification, mucking
//			up our state.
//-----------------------------------------------------------------------------
void CAngleCombo::UpdateAngleBox(char *szText)
{
	if (m_pBox)
	{
		m_pBox->SetDifferentInternal(false);

		if (V_isdigit(szText[0]))
		{
			QAngle vecAngles(0, atoi(szText), 0);
			m_pBox->SetAnglesInternal(vecAngles, true);
		}
		else if (!stricmp(szText, "down"))
		{
			m_pBox->SetAnglesInternal(QAngle(90, 0, 0), true);
		}
		else
		{
			m_pBox->SetAnglesInternal(QAngle(-90, 0, 0), true);
		}
	}
}