// aplNumEdit.cpp : implementation file
//

#include "stdafx.h"
#include "apl_gui.h"

#ifndef TTS_BALLOON
#define TTS_BALLOON	0x40
#endif

#ifndef TTI_NONE

#define TTI_NONE                0
#define TTI_INFO                1
#define TTI_WARNING             2
#define TTI_ERROR               3

#endif

#ifndef TTM_SETTITLEA

#define TTM_SETTITLEA           (WM_USER + 32)  // wParam = TTI_*, lParam = char* szTitle
#define TTM_SETTITLEW           (WM_USER + 33)  // wParam = TTI_*, lParam = wchar* szTitle

#ifdef _UNICODE
#define TTM_SETTITLE            TTM_SETTITLEW
#else
#define TTM_SETTITLE            TTM_SETTITLEA
#endif

#endif


// CaplNumEdit

IMPLEMENT_DYNAMIC(CaplNumEdit, CEdit)

CaplNumEdit::CaplNumEdit()
{
	m_bAllowNegativeValues	= true;
	m_chDecimalChar			= _T('.');
	m_iDigitsAfterDecimal	= 6;
	m_iMaxIntDigits			= 6;
	m_bTTVisible			= false;
	m_bInputValid			= true;
	m_eErrorCode			= aplNone;
	m_bUseToolTips			= true;

	m_nShowDelay			= 700;

	// aplNegativeValue
	m_ErrorDescr.Add( APL_T("Отрицательные числа запрещены"));

	// aplDecimalValue
	m_ErrorDescr.Add( APL_T("Дробные числа запрещены"));

	// aplMaxDigitsAfterDecimal
	m_ErrorDescr.Add( APL_T("Введено максимальное число знаков после разделителя"));

	// aplMaxDigits
	m_ErrorDescr.Add( APL_T("Введено максимальное число знаков"));

	// aplStringValue
	m_ErrorDescr.Add( APL_T("Разрешены только цифры"));

	// aplInvalidFormat
	m_ErrorDescr.Add( APL_T("Неверный формат"));
}

CaplNumEdit::~CaplNumEdit()
{
}

BEGIN_MESSAGE_MAP(CaplNumEdit, CEdit)
	ON_WM_CHAR()
	ON_MESSAGE( WM_PASTE, OnPaste )
	ON_WM_KILLFOCUS()
	ON_WM_SETFOCUS()
	ON_WM_TIMER()
END_MESSAGE_MAP()



// CaplNumEdit message handlers
void CaplNumEdit::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
{
	if (GetStyle() & ES_READONLY)
		return;

	const int CNTRL_C   = 3;
	const int CNTRL_V   = 22;
	const int CNTRL_X   = 24;

	if ((nChar != CNTRL_C && nChar != CNTRL_V  && nChar != CNTRL_X))
	{
		bool bSuccess = ValidateChar(nChar);
		if (bSuccess == false)
		{
			MessageBeep(0);
			ASSERT((m_eErrorCode>=0) && (m_eErrorCode < m_ErrorDescr.GetSize()));
			SetToolTipText(m_ErrorDescr[m_eErrorCode]);
			DisplayToolTip (true);
			return;
		}
		else
			DisplayToolTip (false);

	}

	CEdit::OnChar(nChar, nRepCnt, nFlags);
}

bool CaplNumEdit::ValidateChar( UINT nChar )
{
	bool bValidChar = false;
	CString   strText;
	TCHAR     chText;
	int		  nPosStart, nPosEnd;

	GetWindowText(strText);
	GetSel(nPosStart, nPosEnd);

	if ((nChar <  VK_SPACE ) ||	(nChar >= _T('0') && nChar <= _T('9')))
	{
		bValidChar = true;
	}

	if (nChar == _T('-'))
	{
		if (m_bAllowNegativeValues == true) bValidChar = true;
		else
		{
			if (nPosStart == 0)
			{	
				bValidChar = false;
				m_eErrorCode = aplNegativeValue;
			}
			else
			{
				if (strText[nPosStart - 1] == _T('e') || strText[nPosStart - 1] == _T('E'))
					bValidChar = true;
				else
				{
					bValidChar = false;
					m_eErrorCode = aplInvalidFormat;
				}
			}	
		}
	}

	if (nChar == m_chDecimalChar)
	{
		if (m_iDigitsAfterDecimal > 0) bValidChar = true;
		else
		{
			bValidChar = false;
			m_eErrorCode = aplDecimalValue;
		}
	}

	if (nChar == _T('e') || nChar == _T('E'))
	{
		if (m_iDigitsAfterDecimal > 0) bValidChar = true;
		else
		{
			bValidChar = false;
			m_eErrorCode = aplDecimalValue;
			return false;
		}
	}

	if (nChar > _T('9') && nChar != m_chDecimalChar &&
		nChar != _T('-') && nChar !=  VK_SPACE)
	{
		if (nChar == _T('e') || nChar == _T('E'))
		{
			bValidChar = true;
		}
		else
		{
			bValidChar = false;
			m_eErrorCode = aplStringValue;
		}
	}

	if (nChar == _T('+')) bValidChar = true;

	if (nChar == VK_SPACE) m_eErrorCode = aplStringValue;

	if (!bValidChar)
	{
		if (m_eErrorCode == aplNone) m_eErrorCode = aplStringValue;
		return false;
	}

	if (strText.IsEmpty())
	{
		if (nChar == m_chDecimalChar)
		{
			m_eErrorCode = aplInvalidFormat;
			return false;
		}
	}

	for (int i = 0; i < strText.GetLength(); i++)
	{
		chText = strText[i];

		// максимум один разделитель!
		if ((nChar == m_chDecimalChar) && (chText == (TCHAR)m_chDecimalChar))
		{
			// может это выделение
			if (nPosStart < nPosEnd)
			{
				if (i >= nPosStart && i <= nPosEnd) return true;
			}	
			m_eErrorCode = aplInvalidFormat;
			return false;
		}

		if (nChar == m_chDecimalChar)
		{
			// разделитель первым не может быть
			if (nPosStart == 0)
			{
				m_eErrorCode = aplInvalidFormat;
				return false;
			}

			if (strText[nPosStart - 1] == _T('-') || strText[nPosStart - 1] == _T('+')
				|| strText[nPosStart - 1] == _T('e') || strText[nPosStart - 1] == _T('E'))
			{
				m_eErrorCode = aplInvalidFormat;
				return false;
			}
		}

		if (nChar == _T('e') || nChar == _T('E'))
		{
			// мантиса одна
			if (chText == _T('e') || chText == _T('E'))
			{
				m_eErrorCode = aplInvalidFormat;
				return false;
			}
		}


		// знак отрицания только один!
// 		if ((nChar == _T('-')) && (chText == _T('-')))
// 		{
// 			m_eErrorCode = aplInvalidFormat;
// 			return false;
// 		}

		if (nChar == _T('-'))
		{
			// если позиция не первая, значит проверяем, может число задано с экспонентой
			if (nPosStart != 0 && strText[nPosStart - 1] != _T('e') && strText[nPosStart - 1] != _T('E'))
			{
				m_eErrorCode = aplInvalidFormat;
				return false;	
			}

			// и не последняя
			if (nPosStart != strText.GetLength())
			{
				if (strText[nPosStart] == _T('-') || strText[nPosStart] == _T('+'))
				{
					m_eErrorCode = aplInvalidFormat;
					return false;
				}
			}
		}

		if (nChar == _T('+'))
		{
			if (nPosStart == 0)
			{
				m_eErrorCode = aplInvalidFormat;
				return false;
			}
			
			if (strText[nPosStart - 1] != _T('e') && strText[nPosStart - 1] != _T('E'))
			{
				m_eErrorCode = aplInvalidFormat;
				return false;
			}

			if (nPosStart != strText.GetLength())
			{
				if (strText[nPosStart] == _T('-') || strText[nPosStart] == _T('+'))
				{
					m_eErrorCode = aplInvalidFormat;
					return false;
				}
			}
		}

		// проверка кол-во цифр после разделителя
		if (nChar != VK_BACK)  
		{
			int iPos = strText.Find(m_chDecimalChar);

			if (iPos != -1) 
			{
				// курсор находится до разделителя
				if (nPosStart < iPos || nPosStart == iPos)
				{
					// проверяем на кол-во цифр до разделителя
					if (iPos + 1 > m_iMaxIntDigits)
					{
						m_eErrorCode = aplMaxDigits;
						return false;
					}
				}
				else // после разделителя
				{
					if (nPosStart > (iPos + m_iDigitsAfterDecimal))
					{
						m_eErrorCode = aplMaxDigitsAfterDecimal;
						return false;
					}

					if (nPosStart > iPos  && 
						nPosStart < strText.GetLength() &&
						strText.GetLength() - iPos -1 >= m_iDigitsAfterDecimal)
					{
						m_eErrorCode = aplMaxDigitsAfterDecimal;
						return false;	
					}
				}
			}
			else
			{
				int iSize = strText.GetLength();
				if (strText.Find(_T('-')) != -1) iSize--;

				if (nPosStart != nPosEnd) iSize -= (nPosEnd - nPosStart);
				
				if (iSize + 1 > m_iMaxIntDigits && nChar != m_chDecimalChar && nChar != _T('-'))
				{
					m_eErrorCode = aplMaxDigits;
					return false;
				}
				
			}
		}
	}

	m_eErrorCode = aplNone;
	return true;
}

LRESULT CaplNumEdit::OnPaste( WPARAM, LPARAM )
{
	if ( OpenClipboard() )
	{
#if defined( _UNICODE )
		HANDLE	hClipData = ::GetClipboardData(CF_UNICODETEXT);
#else
		HANDLE	hClipData = ::GetClipboardData(CF_TEXT);
#endif
		if( hClipData )
		{
			LPCTSTR cpText = (LPCTSTR)::GlobalLock(hClipData);
			if (cpText)
			{
				SIZE_T nCount = ::GlobalSize(hClipData);
				TCHAR* cpBuffer = new TCHAR[nCount];
				::_tcscpy( cpBuffer, cpText );
				for (SIZE_T i = 0; i < nCount; i++)
				{
					if (cpBuffer[i] == _T(',') && m_chDecimalChar == _T('.')) cpBuffer[i] = _T('.');
					else if (cpBuffer[i] == _T('.') && m_chDecimalChar == _T(',')) cpBuffer[i] = _T(',');
				}
				if (ValidateString(cpBuffer))
					ReplaceSel( cpBuffer, TRUE );
				else
					MessageBeep(0);

				delete [] cpBuffer;

				::GlobalUnlock( (LPVOID)cpText );
			}
		}
		::CloseClipboard();
	}
	else
	{
		::MessageBeep(0);
	}

	return 0;
}

bool CaplNumEdit::ValidateString( const TCHAR* sStr, bool bReplace /*= true*/ )
{
	CString   strText;			
	int		  nPosStart, nPosEnd;

	m_eErrorCode = aplInvalidFormat;

	if (bReplace)
	{
		GetWindowText(strText);
		GetSel(nPosStart, nPosEnd);
		if ((nPosStart == nPosEnd))
			strText.Insert(nPosStart, sStr);
		else if (nPosStart == 0 && nPosEnd == strText.GetLength())
			strText = sStr;
		else
		{
			strText.Delete(nPosStart, nPosEnd - nPosStart);
			strText.Insert(nPosStart, sStr);
		}
	}
	else
		strText = sStr;

	bool bDecimalFound = false;

	// проверяем на валидность символы
	for (int i = 0; i < strText.GetLength(); i++)
	{
		if (strText[i] == _T('-'))
		{
			if (i > 0 && strText[i - 1] != _T('e') && strText[i - 1] != _T('E'))
				return false;
			else
				continue;
		}

		// разделитель?
		if (strText[i] == (TCHAR)m_chDecimalChar)
		{
			if (m_iDigitsAfterDecimal == 0) return false;

			// разделитель первым нельзя ставить
			if (i == 0) return false;

			// разделитель можно ставить только после цифры!
			if (strText[i - 1] == _T('-')) return false;

			// если раньше разделитель не находили?
			if (bDecimalFound == false)
			{
				bDecimalFound = true;
				continue;
			}
			else return false; // максимум один разделитель
		}
		
		// пробелов нет в числах ))
		if (strText[i] ==  _T(' ')) return false;

		// цифры разрешены
		if (strText[i] >= _T('0') &&  strText[i] <= _T('9')) continue;
		else if (strText[i] == _T('E') || strText[i] == _T('e')) continue;
		else return false;
	}

	int iPos = strText.Find(_T('.'));
	if (iPos > -1)
	{
		// если после разделителя нет ничего, нах посылаем
		if (strText.GetLength() == iPos + 1) return false;

		if (strText.GetLength() - iPos - 1 > m_iDigitsAfterDecimal) return false;
	}
	else
	{
		if (strText.GetLength() > m_iMaxIntDigits) return false;
	}

	m_eErrorCode = aplNone;
	return true;
}

void CaplNumEdit::CreateToolTip( CWnd *pParent, const TCHAR *pszText, const int iIconType, const TCHAR *pszTitle )
{
	TOOLINFO ti;
	DWORD dwStyle = TTS_BALLOON;

	m_ToolTip.Create(pParent, dwStyle);
	m_ToolTip.FillInToolInfo(ti, this, 0);
	ti.uFlags |= TTF_TRACK | TTF_CENTERTIP | TTF_ABSOLUTE;
	ti.lpszText = (LPTSTR) pszText;
#if _MSC_VER >= 1310
	m_ToolTip.SetTitle(iIconType, pszTitle);
#else
	m_ToolTip.SendMessage(TTM_SETTITLE, iIconType, (LPARAM)(LPCTSTR)pszTitle);
#endif
	m_ToolTip.SendMessage (TTM_ADDTOOL, 0, reinterpret_cast<LPARAM> (&ti));
}

void CaplNumEdit::DisplayToolTip( bool bDisplay )
{
	if (m_ToolTip.m_hWnd && m_bUseToolTips)
	{
		CToolInfo ti;

		m_ToolTip.GetToolInfo (ti, this, 0);

		if (bDisplay)
		{
			RECT ClientRect;
			int iHeight = 0;
			int iOffset = 0;

			GetRect (&ClientRect);
			iHeight = ClientRect.bottom - ClientRect.top;
			iOffset = (ClientRect.right - ClientRect.left) / 2;
			::GetWindowRect (m_hWnd, &ti.rect);
			ti.rect.top += iHeight;
			ti.rect.bottom += iHeight;
			ti.rect.left += iOffset;
			ti.rect.right += iOffset;

			m_ToolTip.SendMessage (TTM_TRACKPOSITION, 0, MAKELPARAM (ti.rect.left,
				ti.rect.top));
			m_ToolTip.SendMessage (TTM_TRACKACTIVATE, TRUE,
				reinterpret_cast<LPARAM> (&ti));

			m_nTimer = SetTimer(100, m_nShowDelay, NULL);
		}
		else
		{
			m_ToolTip.SendMessage (TTM_TRACKACTIVATE, FALSE,
				reinterpret_cast<LPARAM> (&ti));
		}

		m_bTTVisible = bDisplay;
	}
}
void CaplNumEdit::OnKillFocus(CWnd* pNewWnd)
{
	/*
	Invalidate();

	CString str;
	GetWindowText(str);
	m_bInputValid = ValidateString(str, false);
	if (m_bInputValid == false)
	{
		this->SetFocus();
		return;
	}
	else
		DisplayToolTip(false);
	*/

	if (pNewWnd && pNewWnd->m_hWnd != m_ToolTip.m_hWnd)
	{
		CEdit::OnKillFocus (pNewWnd);
		DisplayToolTip(false);
	}
}

void CaplNumEdit::OnSetFocus(CWnd* pOldWnd)
{
	CEdit::OnSetFocus(pOldWnd);

	/*
	Invalidate ();

	if (!m_bInputValid)
	{
		SetToolTipText(m_ErrorDescr[m_eErrorCode]);	
		DisplayToolTip (true);
	}
	*/
}

void CaplNumEdit::PreSubclassWindow()
{
	CreateToolTip(this, m_ErrorDescr[(int)aplInvalidFormat], TTI_ERROR,  APL_T("Формат данных"));

	CEdit::PreSubclassWindow();
}

void CaplNumEdit::OnParentMove()
{
	if (m_bTTVisible) DisplayToolTip(false);
}

void CaplNumEdit::AllowDecimalValues( bool bAllow /*= true*/ )
{
	if (bAllow) m_iDigitsAfterDecimal = 6;
	else		m_iDigitsAfterDecimal = 0;
}

void CaplNumEdit::SetMaxDigitsAfterDecimal( int iCount )
{
	if (iCount < 0) m_iDigitsAfterDecimal = 0;
	else			m_iDigitsAfterDecimal = iCount;
}

void CaplNumEdit::SetMaxIntDigits( int iCount )
{
	if (iCount < 0) m_iMaxIntDigits = 1;
	else			m_iMaxIntDigits = iCount;
}

void CaplNumEdit::SetToolTipText (const TCHAR *pszText)
{
	m_ToolTip.UpdateTipText (pszText, this);
}

void CaplNumEdit::ChangeErrorCodeDescr( aplErrorCode eCode, const CString& sDescr )
{
	if (eCode == aplNone) return;
	m_ErrorDescr[(int)eCode] = sDescr;
}

void CaplNumEdit::SetToolTipTitle( const TCHAR* sTitle, int iIconType )
{
	if (m_ToolTip.m_hWnd)
		m_ToolTip.SendMessage(TTM_SETTITLE, iIconType, (LPARAM)(LPCTSTR)sTitle);	
}

BOOL CaplNumEdit::PreTranslateMessage( MSG* pMsg )
{
	/*if(pMsg->message==WM_KEYDOWN)
	{
		UINT nChar=pMsg->wParam;
		if(nChar == VK_OEM_COMMA && m_chDecimalChar==_T('.') ) pMsg->wParam=VK_OEM_PERIOD;
		else if(nChar=VK_OEM_PERIOD && m_chDecimalChar==_T(',')) pMsg->wParam=VK_OEM_COMMA;
	}*/
	if(pMsg->message== WM_CHAR)
	{
		UINT nChar=(UINT)pMsg->wParam;
		if(nChar == _T(',') && m_chDecimalChar==_T('.') ) pMsg->wParam=_T('.');
		else if(nChar == _T('.') && m_chDecimalChar==_T(',') ) pMsg->wParam=_T(',');
	}
	
	return CEdit::PreTranslateMessage(pMsg);
}

void CaplNumEdit::OnTimer( UINT nIDEvent )
{
	switch (nIDEvent)
	{
	case 100:
		KillTimer(m_nTimer);
		DisplayToolTip(false);
		break;
	}	
}
