//
// SpinnerButton.cpp : implementation file
//
/////////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2000 Mikko Mononen <memon@inside.org>
// Feel free to use this file 
//
// This file implements a spinner button control similar
// to the one in 3D Studio MAX. Spinner can automatically
// update it's value to any control (buddy). Output value
// can be integer or double. Internally value is handled in
// floating-point.User can use two arrow buttons to increment
// and decrement the value or use mouse wheel or adjust the
// value by pressing any of the arrow buttons and moving
// the mouse. CTRL-key combined with any operation scales
// the increment by factor of preset multiplier. Value clampping
// can be enabed or disabled and minumum and maximum values
// can be set.
//

#pragma warning( disable : 4786 )		// long names generated by STL

#include "stdafx.h"
#include "demopaja.h"
#include "SpinnerButton.h"
#include <math.h>		// fabs

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CSpinnerButton

CSpinnerButton::CSpinnerButton()
{
	m_up = FALSE;
	m_down = FALSE;
	m_adjusting = FALSE;
	m_timer = 0;
	m_value = 0;
	m_scale = 0.1;
	m_min = 0.0;
	m_max = 100.0;
	m_multiplier = 10.0;
	m_negScale = FALSE;
	m_flags = SPNB_SETBUDDYFLOAT | SPNB_USELIMITS;
	m_buddy = NULL;
}

CSpinnerButton::~CSpinnerButton()
{
}


BEGIN_MESSAGE_MAP(CSpinnerButton, CWnd)
	//{{AFX_MSG_MAP(CSpinnerButton)
	ON_WM_PAINT()
	ON_WM_LBUTTONDOWN()
	ON_WM_LBUTTONUP()
	ON_WM_MOUSEMOVE()
	ON_WM_MOUSEWHEEL()
	ON_WM_SIZE()
	ON_WM_TIMER()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CSpinnerButton message handlers



BOOL CSpinnerButton::Create( DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID ) 
{
    // Register and create the window.
	m_flags = dwStyle & SPNB_ALL;

	CString wndclass = ::AfxRegisterWndClass( 0,
		::LoadCursor( NULL, IDC_ARROW ),
		::GetSysColorBrush( COLOR_BTNFACE ), 0 );

	dwStyle &= ~SPNB_ALL;	// Clear SpinnerButton styles.

	if( !CWnd::Create( wndclass, NULL, dwStyle, rect, pParentWnd, nID ) )
		return FALSE;

	return TRUE;
}

void CSpinnerButton::OnPaint() 
{
	CPaintDC dc(this); // device context for painting

	CBrush*	oldBrush;
	CPen*	oldPen;
	CBrush	blackBrush( ::GetSysColor( COLOR_BTNTEXT ) );
	CPen	blackPen( PS_SOLID, 1, ::GetSysColor( COLOR_BTNTEXT ) );

	oldPen = dc.SelectObject( &blackPen );
	oldBrush = dc.SelectObject( &blackBrush );

	// Boxes
	dc.DrawEdge( m_upRect, m_up ? BDR_SUNKENINNER : BDR_RAISEDINNER, BF_RECT );
	dc.DrawEdge( m_downRect, m_down ? BDR_SUNKENINNER : BDR_RAISEDINNER, BF_RECT );

	POINT	pt[3];

	// Up arrow
	int	xbase = (m_up ? 1 : 0) + m_upRect.right / 2;
	pt[0].x = xbase - m_arrowSize;
	pt[1].x = xbase;
	pt[2].x = xbase + m_arrowSize;

	int	ybase = (m_up ? 1 : 0) + m_upRect.bottom - m_upRect.bottom / 3 - 1;	// bottom value is not included in the rect so we always go off by one.
	pt[0].y = ybase;
	pt[1].y = ybase - m_arrowSize;
	pt[2].y = ybase;

	dc.Polygon( pt, 3 );

	// Down Arrow
	xbase = (m_down ? 1 : 0) + m_downRect.right / 2;
	pt[0].x = xbase - m_arrowSize;
	pt[1].x = xbase;
	pt[2].x = xbase + m_arrowSize;

	ybase = (m_down ? 1 : 0) + m_downRect.top + (m_downRect.bottom - m_downRect.top) / 3;
	pt[0].y = ybase;
	pt[1].y = ybase + m_arrowSize;
	pt[2].y = ybase;

	dc.Polygon( pt, 3 );

	dc.SelectObject( oldPen );
	dc.SelectObject( oldBrush );
}

void CSpinnerButton::OnLButtonDown(UINT nFlags, CPoint point) 
{
	m_adjusting = FALSE;
	m_timerCount = 0;

	if( point.y < m_upRect.bottom ) {
		// User pressed up-button.
		m_up = TRUE;
		m_down = FALSE;
		m_timer = SetTimer( 1, 250, NULL );
		UpdateBuddy( FALSE );
	}
	else {
		// User pressed down-button.
		m_up = FALSE;
		m_down = TRUE;
		m_timer = SetTimer( 1, 250, NULL );
		UpdateBuddy( FALSE );
	}

	// Save inital values
	m_starty = (double)point.y;
	m_startValue = m_value;

	Invalidate();
	SetCapture();
}

void CSpinnerButton::OnMouseMove(UINT nFlags, CPoint point) 
{
	if( nFlags & MK_LBUTTON && GetCapture() == this ) {
		// Kill the timer if it exists
		if( m_timer )
			KillTimer( m_timer );

		double	multiplier = 1.0;
		if( nFlags & MK_CONTROL )
			multiplier = m_multiplier;

		// If we move mouse while pressed, draw both buttons down.
		if( !m_up || !m_down ) {
			m_up = TRUE;
			m_down = TRUE;
			Invalidate();
		}
		m_adjusting = TRUE;
		m_oldCursor = ::SetCursor( ::LoadCursor( NULL, IDC_SIZENS ) );
		m_value = m_startValue - ((double)point.y - m_starty) * m_scale * multiplier;
		UpdateBuddy( TRUE );
	}
}

void CSpinnerButton::OnLButtonUp(UINT nFlags, CPoint point) 
{
	if( m_timer )
		KillTimer( m_timer );

	if( GetCapture() == this ) {

		if( !m_timerCount && !m_adjusting ) {
			// User pressed button down, but no timer events occured yet nor
			// did the user use the adjusting mode (move mouse). Thread this case
			// as single button press.
			double	multiplier = 1.0;
			if( nFlags & MK_CONTROL )
				multiplier = m_multiplier;

			if( m_up )
				m_value += m_scale * multiplier;
			else
				m_value -= m_scale * multiplier;
		}

		m_up = FALSE;
		m_down = FALSE;

		if( m_adjusting ) {
			::SetCursor( m_oldCursor );
			m_adjusting = FALSE;
		}

		Invalidate();
		ReleaseCapture();

		
		if( m_value != m_startValue && m_buddy ) {
			// Notify anything that updates its value in WM_KILLFOCUS
			m_buddy->SetFocus();
		}

		UpdateBuddy( TRUE );

		if( m_value != m_startValue && m_buddy ) {
			// Notify anything that updates its value in WM_KILLFOCUS
			SetFocus();
		}
	}
}

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

	int	width = !(cx & 1) ? cx - 1 : cx;
	int	height = cy / 2;

	m_arrowSize = (width > height ? height : width) / 3;

	m_upRect.left = 0;
	m_upRect.right = width;
	m_upRect.top = 0;
	m_upRect.bottom = height;

	m_downRect.left = 0;
	m_downRect.right = width;
	m_downRect.top = cy - height;
	m_downRect.bottom = cy;

}

void CSpinnerButton::OnTimer(UINT nIDEvent) 
{
	// Timer is set on when user presses up or down button.
	// Each time timer event occurs value is incremented or
	// decremented depending which button is pressed.
	if( nIDEvent == 1 ) {
		if( m_up )
			m_value += m_scale;
		else
			m_value -= m_scale;
		m_startValue = m_value;
		UpdateBuddy( TRUE );
		m_timerCount++;
	}
}

CWnd* CSpinnerButton::SetBuddy( CWnd* buddy, DWORD nFlags )
{
	CWnd*	old = m_buddy;
	m_buddy = buddy;

	if( m_buddy && nFlags & SPNB_ATTACH_LEFT ) {
		// Move spinner left of buddy window
		if( GetParent() ) {
			CRect	rect;
			m_buddy->GetWindowRect( rect );
			GetParent()->ScreenToClient( rect );
			rect.DeflateRect( 1, 1 );
			rect.right = rect.left - 3;
			rect.left = rect.right - (rect.Height() * 4 / 5);
			MoveWindow( rect, TRUE );
			OnSize( 0, rect.Width(), rect.Height() );
		}
	}
	else if( m_buddy && nFlags & SPNB_ATTACH_RIGHT ) {
		// Move spinner right of buddy window
		if( GetParent() ) {
			CRect	rect;
			m_buddy->GetWindowRect( rect );
			GetParent()->ScreenToClient( rect );
			rect.DeflateRect( 1, 1 );
			rect.left = rect.right + 3;
			rect.right = rect.left + (rect.Height() * 4 / 5);
			MoveWindow( rect, TRUE );
			OnSize( 0, rect.Width(), rect.Height() );
		}
	}

	return old;
}

CWnd* CSpinnerButton::GetBuddy() const
{
	return m_buddy;
}

void CSpinnerButton::SetScale( double scale )
{
	m_scale = fabs( scale );
	if( m_negScale )
		m_scale = -m_scale;
}

void CSpinnerButton::SetMinMax( double min, double max )
{
	// If min is alrger than max use negative scaling and
	// swap input values for min/max. This allows us to handle
	// values more easily. m_negScale flag is set to be able
	// to assign correct sign to the scale value in SetScale().
	if( min > max ) {
		m_min = max;
		m_max = min;
		m_scale = -fabs( m_scale );
		m_negScale = TRUE;
	}
	else {
		m_min = min;
		m_max = max;
		m_scale = fabs( m_scale );
		m_negScale = FALSE;
	}
}

double CSpinnerButton::GetMin() const
{
	return m_negScale ? m_max : m_min;
}

double CSpinnerButton::GetMax() const
{
	return m_negScale ? m_min : m_max;
}

void CSpinnerButton::UpdateBuddy( BOOL save )
{
	if( save ) {
		// Save value to buddy

		// Clamp value.
		if( m_flags & SPNB_USELIMITS ) {
			if( m_value < m_min )
				m_value = m_min;
			if( m_value > m_max )
				m_value = m_max;
		}

		// If buddy set buddy wnd's text.
		if( m_buddy ) {
			CString	str;
			if( m_flags & SPNB_SETBUDDYINT )
				str.Format( "%d", (int)m_value );
			else if( m_flags & SPNB_SETBUDDYFLOAT ) {
				str.Format( "%f00", m_value );
				// delete trailing zeroes
				for( int j = str.GetLength() - 1; j >= 0; j-- ) {
					if( str.GetAt( j ) != '0' ) {
						if( str.GetAt( j ) == '.' ) str.SetAt( j, '\0' );
						else  str.SetAt( j + 1, '\0' );
						break;
					}
				}
			}
			m_buddy->SetWindowText( str );
		}

	}
	else {
		// Get value from buddy
		if( m_buddy ) {
			CString	str;
			m_buddy->GetWindowText( str );
			m_value = atof( str );

			// Clamp value
			if( m_flags & SPNB_USELIMITS ) {
				if( m_value < m_min )
					m_value = m_min;
				if( m_value > m_max )
					m_value = m_max;
			}
		}
	}
}

void CSpinnerButton::SetVal( double val )
{
	m_value = val;
}

double CSpinnerButton::GetVal() const
{
	return m_value;
}

void CSpinnerButton::SetMultiplier( double val )
{
	m_multiplier = val;
}

double CSpinnerButton::GetMultiplier() const
{
	return m_multiplier;
}
