661 lines
13 KiB
C++
661 lines
13 KiB
C++
// NumberCtrl.cpp : implementation file
|
|
//
|
|
|
|
#include "stdafx.h"
|
|
#include "NumberCtrl.h"
|
|
|
|
#ifdef _DEBUG
|
|
#define new DEBUG_NEW
|
|
#undef THIS_FILE
|
|
static char THIS_FILE[] = __FILE__;
|
|
#endif
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// CNumberCtrl
|
|
|
|
CNumberCtrl::CNumberCtrl()
|
|
{
|
|
m_btnStatus = 0;
|
|
m_btnWidth = 10;
|
|
m_draggin = false;
|
|
m_value = 0;
|
|
m_min = 0;
|
|
m_max = 10000;
|
|
m_step = 0.01f;
|
|
m_enabled = true;
|
|
m_noNotify = false;
|
|
m_integer = false;
|
|
m_iInternalPrecision=2; // default internal precision for floats
|
|
m_nFlags = 0;
|
|
m_bUndoEnabled = false;
|
|
m_bDragged = false;
|
|
m_multiplier = 1;
|
|
}
|
|
|
|
CNumberCtrl::~CNumberCtrl()
|
|
{
|
|
}
|
|
|
|
|
|
BEGIN_MESSAGE_MAP(CNumberCtrl, CWnd)
|
|
//{{AFX_MSG_MAP(CNumberCtrl)
|
|
ON_WM_CREATE()
|
|
ON_WM_PAINT()
|
|
ON_WM_MOUSEMOVE()
|
|
ON_WM_LBUTTONDOWN()
|
|
ON_WM_LBUTTONUP()
|
|
ON_WM_ENABLE()
|
|
ON_WM_SETFOCUS()
|
|
ON_EN_SETFOCUS(IDC_EDIT,OnEditSetFocus)
|
|
ON_EN_KILLFOCUS(IDC_EDIT,OnEditKillFocus)
|
|
//}}AFX_MSG_MAP
|
|
ON_WM_SIZE()
|
|
END_MESSAGE_MAP()
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// CNumberCtrl message handlers
|
|
|
|
void CNumberCtrl::Create( CWnd* parentWnd,CRect &rc,UINT nID,int nFlags )
|
|
{
|
|
m_nFlags = nFlags;
|
|
HCURSOR arrowCursor = AfxGetApp()->LoadStandardCursor( IDC_ARROW );
|
|
CreateEx( 0,AfxRegisterWndClass(NULL,arrowCursor,NULL/*(HBRUSH)GetStockObject(LTGRAY_BRUSH)*/),NULL,WS_CHILD|WS_VISIBLE|WS_TABSTOP,rc,parentWnd,nID );
|
|
}
|
|
|
|
void CNumberCtrl::Create( CWnd* parentWnd,UINT ctrlID,int flags )
|
|
{
|
|
ASSERT( parentWnd );
|
|
m_nFlags = flags;
|
|
CRect rc;
|
|
CWnd *ctrl = parentWnd->GetDlgItem( ctrlID );
|
|
ctrl->SetDlgCtrlID( ctrlID + 10000 );
|
|
ctrl->ShowWindow( SW_HIDE );
|
|
ctrl->GetWindowRect( rc );
|
|
parentWnd->ScreenToClient(rc);
|
|
|
|
HCURSOR arrowCursor = AfxGetApp()->LoadStandardCursor( IDC_ARROW );
|
|
CreateEx( 0,AfxRegisterWndClass(NULL,arrowCursor,NULL/*(HBRUSH)GetStockObject(LTGRAY_BRUSH)*/),NULL,WS_CHILD|WS_VISIBLE|WS_TABSTOP,rc,parentWnd,ctrlID );
|
|
}
|
|
|
|
int CNumberCtrl::OnCreate(LPCREATESTRUCT lpCreateStruct)
|
|
{
|
|
if (CWnd::OnCreate(lpCreateStruct) == -1)
|
|
return -1;
|
|
|
|
m_upDownCursor = AfxGetApp()->LoadStandardCursor( IDC_SIZENS );
|
|
m_upArrow = (HICON)LoadImage( AfxGetInstanceHandle(),MAKEINTRESOURCE(IDI_UP_ARROW),IMAGE_ICON,5,5,LR_DEFAULTCOLOR );
|
|
m_downArrow = (HICON)LoadImage( AfxGetInstanceHandle(),MAKEINTRESOURCE(IDI_DOWN_ARROW),IMAGE_ICON,5,5,LR_DEFAULTCOLOR );
|
|
|
|
CRect rc;
|
|
GetClientRect( rc );
|
|
|
|
if (m_nFlags & LEFTARROW)
|
|
{
|
|
rc.left += m_btnWidth+3;
|
|
}
|
|
else
|
|
{
|
|
rc.right -= m_btnWidth+1;
|
|
}
|
|
|
|
DWORD nFlags = WS_CHILD|WS_VISIBLE|WS_TABSTOP|ES_AUTOHSCROLL;
|
|
if (m_nFlags & LEFTALIGN)
|
|
nFlags |= ES_LEFT;
|
|
else
|
|
nFlags |= ES_RIGHT;
|
|
|
|
if (!(m_nFlags & NOBORDER))
|
|
nFlags |= WS_BORDER;
|
|
//m_edit.Create( WS_CHILD|WS_VISIBLE|WS_TABSTOP|WS_BORDER|ES_RIGHT|ES_AUTOHSCROLL,rc,this,IDC_EDIT );
|
|
m_edit.Create( nFlags,rc,this,IDC_EDIT );
|
|
// m_edit.Create( WS_CHILD|WS_VISIBLE|WS_TABSTOP|WS_BORDER|ES_LEFT|ES_AUTOHSCROLL,rc,this,IDC_EDIT );
|
|
m_edit.SetFont( CFont::FromHandle( (HFONT)::GetStockObject(DEFAULT_GUI_FONT)) );
|
|
m_edit.SetUpdateCallback( functor(*this, &CNumberCtrl::OnEditChanged) );
|
|
|
|
float val = m_value;
|
|
m_value = val+1;
|
|
SetInternalValue( val );
|
|
|
|
return 0;
|
|
}
|
|
|
|
void CNumberCtrl::SetLeftAlign( bool left )
|
|
{
|
|
if (m_edit)
|
|
{
|
|
if (left)
|
|
m_edit.ModifyStyle( ES_RIGHT,ES_LEFT );
|
|
else
|
|
m_edit.ModifyStyle( ES_LEFT,ES_RIGHT );
|
|
}
|
|
}
|
|
|
|
void CNumberCtrl::OnPaint()
|
|
{
|
|
CPaintDC dc(this); // device context for painting
|
|
|
|
DrawButtons( dc );
|
|
|
|
// Do not call CWnd::OnPaint() for painting messages
|
|
}
|
|
|
|
void CNumberCtrl::DrawButtons( CDC &dc )
|
|
{
|
|
CRect rc;
|
|
GetClientRect( rc );
|
|
|
|
if (!m_enabled)
|
|
{
|
|
//dc.FillRect( rc,CBrush::FromHandle((HBRUSH)GetStockObject(LTGRAY_BRUSH)) );
|
|
dc.FillSolidRect( rc,GetSysColor(COLOR_3DFACE) );
|
|
return;
|
|
}
|
|
int x = 0;
|
|
if (!(m_nFlags & LEFTARROW))
|
|
{
|
|
x = rc.right-m_btnWidth;
|
|
}
|
|
int y = rc.top;
|
|
int w = m_btnWidth;
|
|
int h = rc.bottom;
|
|
int h2 = h/2;
|
|
COLORREF hilight = RGB(255,255,255);
|
|
COLORREF shadow = RGB(100,100,100);
|
|
//dc.Draw3dRect( x,y,w,h/2-1,hilight,shadow );
|
|
//dc.Draw3dRect( x,y+h/2+1,w,h/2-1,hilight,shadow );
|
|
|
|
int smallOfs = 0;
|
|
if (rc.bottom <= 18)
|
|
smallOfs = 1;
|
|
|
|
|
|
//dc.FillRect()
|
|
//dc.SelectObject(b1);
|
|
//dc.Rectangle( x,y, x+w,y+h2 );
|
|
//dc.SelectObject(b2);
|
|
//dc.Rectangle( x,y+h2, x+w,y+h );
|
|
|
|
if (m_btnStatus == 1 || m_btnStatus == 3)
|
|
{
|
|
dc.Draw3dRect( x,y,w,h2,shadow,hilight );
|
|
}
|
|
else
|
|
{
|
|
dc.Draw3dRect( x,y,w,h2,hilight,shadow );
|
|
//DrawIconEx( dc,x+1,y+2,m_upArrow,5,5,0,0,DI_NORMAL );
|
|
}
|
|
|
|
if (m_btnStatus == 2 || m_btnStatus == 3)
|
|
dc.Draw3dRect( x,y+h2+1,w,h2-1+smallOfs,shadow,hilight );
|
|
else
|
|
dc.Draw3dRect( x,y+h2+1,w,h2-1+smallOfs,hilight,shadow );
|
|
|
|
DrawIconEx( dc,x+2,y+2,m_upArrow,5,5,0,0,DI_NORMAL );
|
|
|
|
DrawIconEx( dc,x+2,y+h2+3-smallOfs,m_downArrow,5,5,0,0,DI_NORMAL );
|
|
}
|
|
|
|
void CNumberCtrl::GetBtnRect( int btn,CRect &rc )
|
|
{
|
|
CRect rcw;
|
|
GetClientRect( rcw );
|
|
|
|
int x = 0;
|
|
if (!(m_nFlags & LEFTARROW))
|
|
{
|
|
x = rcw.right-m_btnWidth;
|
|
}
|
|
int y = rcw.top;
|
|
int w = m_btnWidth;
|
|
int h = rcw.bottom;
|
|
int h2 = h/2;
|
|
|
|
if (btn == 0)
|
|
{
|
|
rc.SetRect( x,y,x+w,y+h2 );
|
|
}
|
|
else if (btn == 1)
|
|
{
|
|
rc.SetRect( x,y+h2+1,x+w,y+h );
|
|
}
|
|
}
|
|
|
|
int CNumberCtrl::GetBtn( CPoint point )
|
|
{
|
|
for (int i = 0; i < 2; i++)
|
|
{
|
|
CRect rc;
|
|
GetBtnRect( i,rc );
|
|
if (rc.PtInRect(point))
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
void CNumberCtrl::SetBtnStatus( int s )
|
|
{
|
|
m_btnStatus = s;
|
|
RedrawWindow();
|
|
}
|
|
|
|
void CNumberCtrl::OnLButtonDown(UINT nFlags, CPoint point)
|
|
{
|
|
if (!m_enabled)
|
|
return;
|
|
|
|
m_btnStatus = 0;
|
|
int btn = GetBtn(point);
|
|
if (btn >= 0)
|
|
{
|
|
SetBtnStatus( btn+1 );
|
|
m_bDragged = false;
|
|
|
|
// Start undo.
|
|
if (m_bUndoEnabled)
|
|
GetIEditor()->BeginUndo();
|
|
}
|
|
m_mousePos = point;
|
|
SetCapture();
|
|
m_draggin = true;
|
|
|
|
CWnd::OnLButtonDown(nFlags, point);
|
|
}
|
|
|
|
void CNumberCtrl::OnLButtonUp(UINT nFlags, CPoint point)
|
|
{
|
|
if (!m_enabled)
|
|
return;
|
|
|
|
bool bLButonDown = false;
|
|
m_draggin = false;
|
|
if (GetCapture() == this)
|
|
{
|
|
bLButonDown = true;
|
|
ReleaseCapture();
|
|
}
|
|
SetBtnStatus( 0 );
|
|
|
|
//if (m_endUpdateCallback)
|
|
//m_endUpdateCallback(this);
|
|
|
|
if (m_bUndoEnabled && GetIEditor()->IsUndoRecording())
|
|
GetIEditor()->AcceptUndo( m_undoText );
|
|
|
|
if (bLButonDown)
|
|
{
|
|
int btn = GetBtn(point);
|
|
if (!m_bDragged && btn >= 0)
|
|
{
|
|
float prevValue = m_value;
|
|
if (btn == 0)
|
|
SetInternalValue( GetInternalValue() + m_step );
|
|
if (btn == 1)
|
|
SetInternalValue( GetInternalValue() - m_step );
|
|
|
|
if (prevValue != m_value)
|
|
NotifyUpdate(false);
|
|
}
|
|
else if (m_bDragged)
|
|
{
|
|
// Send last non tracking update after tracking.
|
|
NotifyUpdate(false);
|
|
}
|
|
}
|
|
|
|
///CWnd::OnLButtonUp(nFlags, point);
|
|
|
|
if (m_edit)
|
|
m_edit.SetFocus();
|
|
}
|
|
|
|
void CNumberCtrl::OnMouseMove(UINT nFlags, CPoint point)
|
|
{
|
|
if (!m_enabled)
|
|
return;
|
|
|
|
if (point == m_mousePos)
|
|
return;
|
|
|
|
if (m_draggin)
|
|
{
|
|
m_bDragged = true;
|
|
float prevValue = m_value;
|
|
|
|
SetCursor( m_upDownCursor );
|
|
if (m_btnStatus != 3)
|
|
SetBtnStatus(3);
|
|
|
|
if (!m_integer)
|
|
{
|
|
// Floating control.
|
|
int y = (point.y-m_mousePos.y) * abs((point.y-m_mousePos.y));
|
|
SetInternalValue( GetInternalValue() - m_step*y );
|
|
}
|
|
else
|
|
{
|
|
// Integer control.
|
|
int y = point.y-m_mousePos.y;
|
|
SetInternalValue( GetInternalValue() - m_step*y );
|
|
}
|
|
|
|
CPoint cp;
|
|
GetCursorPos( &cp );
|
|
int sX = GetSystemMetrics(SM_CXSCREEN);
|
|
int sY = GetSystemMetrics(SM_CYSCREEN);
|
|
|
|
if (cp.y < 20 || cp.y > sY-20)
|
|
{
|
|
// When near end of screen, prevent cursor from moving.
|
|
CPoint p = m_mousePos;
|
|
ClientToScreen(&p);
|
|
SetCursorPos( p.x,p.y );
|
|
}
|
|
else
|
|
{
|
|
m_mousePos = point;
|
|
}
|
|
|
|
if (prevValue != m_value)
|
|
NotifyUpdate(true);
|
|
}
|
|
|
|
CWnd::OnMouseMove(nFlags, point);
|
|
}
|
|
|
|
void CNumberCtrl::SetRange( float min,float max )
|
|
{
|
|
m_min = min;
|
|
m_max = max;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CNumberCtrl::SetInternalValue( float val )
|
|
{
|
|
CString s;
|
|
|
|
if (val < m_min) val = m_min;
|
|
if (val > m_max) val = m_max;
|
|
|
|
if (m_integer)
|
|
{
|
|
if ((int)m_value == (int)val)
|
|
return;
|
|
s.Format( "%d",(int)val );
|
|
}
|
|
else
|
|
{
|
|
double prec = pow(10.0f,m_iInternalPrecision);
|
|
if (fabs(val-m_value) < 0.9f/prec)
|
|
return;
|
|
|
|
/*
|
|
if (val == int(val))
|
|
{
|
|
// Optimize float to string storage.
|
|
s.Format( "%i",int(val) );
|
|
}
|
|
else
|
|
{
|
|
int digits = CalculateDecimalPlaces(val,m_iInternalPrecision); // m.m. calculate the digits right from the comma
|
|
char fmt[12];
|
|
|
|
if (digits < 2)
|
|
digits = 2;
|
|
sprintf(fmt,"%%.%df",digits); // e.g. "%.2f"
|
|
s.Format( fmt,val );
|
|
}
|
|
*/
|
|
double v = val;
|
|
v = v*(prec);
|
|
int intpart = RoundFloatToInt(v);
|
|
v = (double)intpart / prec;
|
|
// Round it to precision.
|
|
s.Format( "%g",v );
|
|
}
|
|
m_value = val;
|
|
|
|
if (m_edit)
|
|
{
|
|
m_noNotify = true;
|
|
m_edit.SetText( s );
|
|
m_noNotify = false;
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
float CNumberCtrl::GetInternalValue() const
|
|
{
|
|
if (!m_enabled)
|
|
return m_value;
|
|
|
|
if (m_edit)
|
|
{
|
|
CString str;
|
|
m_edit.GetWindowText(str);
|
|
m_value = atof( (const char*)str );
|
|
}
|
|
return m_value;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CNumberCtrl::SetValue( float val,float step )
|
|
{
|
|
m_step = step;
|
|
if (m_integer && m_step < 1)
|
|
m_step = 1;
|
|
SetInternalValue( val*m_multiplier );
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
float CNumberCtrl::GetValue() const
|
|
{
|
|
return GetInternalValue() / m_multiplier;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
float CNumberCtrl::GetStep() const
|
|
{
|
|
return m_step;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
CString CNumberCtrl::GetValueAsString() const
|
|
{
|
|
CString str;
|
|
/*
|
|
int digits = CalculateDecimalPlaces(GetValue(),m_iInternalPrecision); // m.m. calculate the digits right from the comma
|
|
char fmt[12];
|
|
sprintf(fmt,"%%.%df",digits); // e.g. "%.2f"
|
|
str.Format( fmt,m_value );
|
|
*/
|
|
str.Format( "%g",m_value/m_multiplier );
|
|
return str;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CNumberCtrl::OnEditChanged()
|
|
{
|
|
static bool inUpdate = false;
|
|
|
|
if (!inUpdate)
|
|
{
|
|
float prevValue = m_value;
|
|
|
|
float v = GetInternalValue();
|
|
if (v < m_min) v = m_min;
|
|
if (v > m_max) v = m_max;
|
|
if (v != m_value)
|
|
{
|
|
SetInternalValue( v );
|
|
}
|
|
|
|
if (prevValue != m_value)
|
|
NotifyUpdate(false);
|
|
|
|
inUpdate = false;
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CNumberCtrl::OnEnable(BOOL bEnable)
|
|
{
|
|
CWnd::OnEnable(bEnable);
|
|
|
|
m_enabled = (bEnable == TRUE);
|
|
if (m_edit)
|
|
{
|
|
m_edit.EnableWindow(bEnable);
|
|
RedrawWindow();
|
|
}
|
|
}
|
|
|
|
void CNumberCtrl::NotifyUpdate( bool tracking )
|
|
{
|
|
if (m_noNotify)
|
|
return;
|
|
|
|
if (!tracking && m_bUndoEnabled)
|
|
GetIEditor()->BeginUndo();
|
|
|
|
if (m_updateCallback)
|
|
m_updateCallback(this);
|
|
|
|
CWnd *parent = GetParent();
|
|
if (parent)
|
|
{
|
|
if (!tracking)
|
|
{
|
|
::SendMessage( parent->GetSafeHwnd(),WM_COMMAND,MAKEWPARAM( GetDlgCtrlID(),EN_CHANGE ),(LPARAM)GetSafeHwnd() );
|
|
}
|
|
::SendMessage( parent->GetSafeHwnd(),WM_COMMAND,MAKEWPARAM( GetDlgCtrlID(),EN_UPDATE ),(LPARAM)GetSafeHwnd() );
|
|
}
|
|
m_lastUpdateValue = m_value;
|
|
|
|
if (!tracking && m_bUndoEnabled)
|
|
GetIEditor()->AcceptUndo( m_undoText );
|
|
}
|
|
|
|
void CNumberCtrl::OnSetFocus(CWnd* pOldWnd)
|
|
{
|
|
CWnd::OnSetFocus(pOldWnd);
|
|
|
|
if (m_edit)
|
|
{
|
|
m_edit.SetFocus();
|
|
m_edit.SetSel(0,-1);
|
|
}
|
|
}
|
|
|
|
void CNumberCtrl::SetInteger( bool enable )
|
|
{
|
|
m_integer = enable;
|
|
m_step = 1;
|
|
float f = GetInternalValue();
|
|
m_value = f+1;
|
|
SetInternalValue(f);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CNumberCtrl::OnEditSetFocus()
|
|
{
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CNumberCtrl::OnEditKillFocus()
|
|
{
|
|
//if (m_bUpdating && m_endUpdateCallback)
|
|
//m_endUpdateCallback(this);
|
|
|
|
if (m_value != m_lastUpdateValue)
|
|
NotifyUpdate(false);
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// calculate the digits right from the comma
|
|
int CNumberCtrl::CalculateDecimalPlaces( float infNumber, int iniMaxPlaces )
|
|
{
|
|
assert(iniMaxPlaces>=0);
|
|
|
|
char str[256],*_str=str;
|
|
|
|
sprintf(str,"%f",infNumber);
|
|
|
|
while(*_str!='.')_str++; // search the comma
|
|
|
|
int ret=0;
|
|
|
|
if(*_str!=0) // comma?
|
|
{
|
|
int cur=1;
|
|
|
|
_str++; // jump over comma
|
|
|
|
while(*_str>='0' && *_str<='9')
|
|
{
|
|
if(*_str!='0')ret=cur;
|
|
|
|
_str++;cur++;
|
|
}
|
|
|
|
if(ret>iniMaxPlaces)ret=iniMaxPlaces; // bound to maximum
|
|
}
|
|
|
|
return(ret);
|
|
}
|
|
|
|
|
|
|
|
//! m.m. (default is 2)
|
|
void CNumberCtrl::SetInternalPrecision( int iniDigits )
|
|
{
|
|
assert(iniDigits>=0);
|
|
|
|
m_iInternalPrecision=iniDigits;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CNumberCtrl::EnableUndo( const CString& undoText )
|
|
{
|
|
m_undoText = undoText;
|
|
m_bUndoEnabled = true;
|
|
}
|
|
|
|
void CNumberCtrl::OnSize(UINT nType, int cx, int cy)
|
|
{
|
|
CWnd::OnSize(nType, cx, cy);
|
|
|
|
if (m_edit.m_hWnd)
|
|
{
|
|
CRect rc;
|
|
GetClientRect( rc );
|
|
if (m_nFlags & LEFTARROW)
|
|
{
|
|
rc.left += m_btnWidth+3;
|
|
}
|
|
else
|
|
{
|
|
rc.right -= m_btnWidth+1;
|
|
}
|
|
m_edit.MoveWindow(rc,FALSE);
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CNumberCtrl::SetFont( CFont* pFont,BOOL bRedraw )
|
|
{
|
|
CWnd::SetFont(pFont,bRedraw);
|
|
if (m_edit.m_hWnd)
|
|
m_edit.SetFont(pFont,bRedraw);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CNumberCtrl::SetMultiplier( float fMultiplier )
|
|
{
|
|
if (fMultiplier != 0)
|
|
m_multiplier = fMultiplier;
|
|
} |