/////////////////////////////////////////////////////////////////////////////
// Name:        spinctld.h
// Author:      John Labenski
// Created:     11/05/02
// Copyright:   John Labenski, 2002
// License:     wxWidgets
/////////////////////////////////////////////////////////////////////////////

#if defined(__GNUG__) && !defined(NO_GCC_PRAGMA)
    #pragma implementation "spinctld.h"
#endif

// For compilers that support precompilation, includes "wx/wx.h".
#include "wx/wxprec.h"

#ifdef __BORLANDC__
    #pragma hdrstop
#endif

#ifndef WX_PRECOMP
    #include "wx/valtext.h"     // for wxTextValidator
    #include "wx/textctrl.h"
#endif // WX_PRECOMP

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

#if wxMINOR_VERSION > 4
    #include "wx/math.h"
#else
#if defined(__VISUALC__) || defined(__BORLANDC__) || defined(__WATCOMC__)
    #include <float.h>
    #define wxFinite(x) _finite(x)
#elif defined(__GNUG__)||defined(__GNUWIN32__)||defined(__DJGPP__)|| \
      defined(__SGI_CC__)||defined(__SUNCC__)||defined(__XLC__)|| \
          defined(__HPUX__)||defined(__MWERKS__)
    #define wxFinite(x) finite(x)
#else
    #define wxFinite(x) ((x) == (x))
#endif
#endif

// NOTES : if the textctrl is focused and the program is ending, a killfocus
//         event is sent in MSW, this is why m_textCtrl is set to NULL in it's
//         destructor and there's so many checks for it not being NULL

//----------------------------------------------------------------------------
// wxSpinCtrlDbl
//----------------------------------------------------------------------------

// the textctrl used for the wxSpinCtrlDbl, needed for keypresses
class wxSpinCtrlDblTextCtrl : public wxTextCtrl
{
public:
    wxSpinCtrlDblTextCtrl( wxWindow *parent, wxWindowID id,
                           const wxString &value = wxEmptyString,
                           const wxPoint &pos = wxDefaultPosition,
                           const wxSize &size = wxDefaultSize,
                           long style = 0,
                           const wxValidator& validator = wxDefaultValidator,
                           const wxString &name = wxTextCtrlNameStr);

    // MSW sends extra kill focus event
    virtual ~wxSpinCtrlDblTextCtrl()
    {
        if (m_parent) m_parent->m_textCtrl = NULL;
        m_parent = NULL;
    }

    wxSpinCtrlDbl *m_parent;

    void OnChar( wxKeyEvent& e );         // pass chars to wxSpinCtrlDbl
    void OnKillFocus( wxFocusEvent& e );  // sync the spin to textctrl

private:
    DECLARE_EVENT_TABLE()
};

BEGIN_EVENT_TABLE(wxSpinCtrlDblTextCtrl,wxTextCtrl)
//	EVT_TEXT_ENTER( wxID_ANY, wxSpinCtrlDblTextCtrl::OnTextEnter ) // get them from spinctrldbl
//  EVT_TEXT( wxID_ANY, wxSpinCtrlDblTextCtrl::OnTextUpdate )      // get them from spinctrldbl
    EVT_CHAR( wxSpinCtrlDblTextCtrl::OnChar )
    EVT_KILL_FOCUS( wxSpinCtrlDblTextCtrl::OnKillFocus )
END_EVENT_TABLE()

wxSpinCtrlDblTextCtrl::wxSpinCtrlDblTextCtrl( wxWindow *parent, wxWindowID id,
                                              const wxString &value,
                                              const wxPoint &pos, const wxSize &size,
                                              long style,
                                              const wxValidator& validator,
                                              const wxString &name)
	: wxTextCtrl( parent, id, value, pos, size, style, validator, name)
{
    m_parent = (wxSpinCtrlDbl*)parent;
}

void wxSpinCtrlDblTextCtrl::OnChar( wxKeyEvent &event )
{
    if (m_parent) m_parent->OnChar( event );
}

void wxSpinCtrlDblTextCtrl::OnKillFocus( wxFocusEvent &event )
{
    if (m_parent) m_parent->SyncSpinToText(true);
    event.Skip();
}

//----------------------------------------------------------------------------
// wxSpinCtrlDbl
//----------------------------------------------------------------------------

IMPLEMENT_DYNAMIC_CLASS( wxSpinCtrlDbl, wxControl )

BEGIN_EVENT_TABLE(wxSpinCtrlDbl,wxControl)
    EVT_SPIN_UP   ( wxID_ANY, wxSpinCtrlDbl::OnSpinUp    )
    EVT_SPIN_DOWN ( wxID_ANY, wxSpinCtrlDbl::OnSpinDown  )
    EVT_TEXT_ENTER( wxID_ANY, wxSpinCtrlDbl::OnTextEnter )
    //EVT_TEXT      ( wxID_ANY, wxSpinCtrlDbl::OnText      )
    EVT_SET_FOCUS ( wxSpinCtrlDbl::OnFocus     )
    EVT_KILL_FOCUS( wxSpinCtrlDbl::OnKillFocus )
END_EVENT_TABLE()

void wxSpinCtrlDbl::Init()
{
    m_min = 0;
    m_max = 100;
    m_value = 0;
    m_default_value = 0;
    m_increment = 1;
    m_digits = wxSPINCTRLDBL_AUTODIGITS;
    m_snap_ticks = false;
    m_spinButton = NULL;
    m_textCtrl = NULL;
}

bool wxSpinCtrlDbl::Create( wxWindow *parent, wxWindowID id,
                            const wxString& value,
                            const wxPoint& pos, const wxSize& size, 
                            long style,
                            double min, double max,
                            double initial,
                            double increment, int digits,
                            const wxString& name)
{
    if (!wxControl::Create(parent, id, pos, size, style|wxNO_BORDER, 
                           wxDefaultValidator, name))
        return false;
    
    wxControl::SetLabel(name);
    wxControl::SetBackgroundColour(parent->GetBackgroundColour());
    wxControl::SetForegroundColour(parent->GetForegroundColour());

    int width = size.GetWidth(), height = size.GetHeight();

    wxSize best_size( DoGetBestSize() );
    if (width  == -1) width  = best_size.GetWidth();
    if (height == -1) height = best_size.GetHeight();

    // Create a validator for numbers, +-, and eE for exponential
    wxTextValidator validator(wxFILTER_INCLUDE_CHAR_LIST);
    
#if wxCHECK_VERSION(2, 5, 4)    
    wxArrayString list;

    wxString valid_chars(wxT(" 0123456789+-.eE"));
    size_t len = valid_chars.Length();
    for (size_t i=0; i<len; i++)
        list.Add(wxString(valid_chars.GetChar(i)));

    validator.SetIncludes(list);
#else
    wxStringList list;

    wxString valid_chars(wxT(" 0123456789+-.eE"));
    size_t len = valid_chars.Length();
    for (size_t i=0; i<len; i++)
        list.Add(wxString(valid_chars.GetChar(i)));

    validator.SetIncludeList(list);
#endif // wxCHECK_VER(2, 5, 4)   
    
    m_spinButton = new wxSpinButton( this, id, wxPoint(0,0), wxSize(-1, height),
                                     wxSP_ARROW_KEYS|wxSP_VERTICAL|wxSP_WRAP);
    m_textCtrl = new wxSpinCtrlDblTextCtrl( this, id, value, 
                      wxPoint(0,0),
                      wxSize(width-m_spinButton->GetSize().GetWidth(), height),
                      wxTE_NOHIDESEL|wxTE_PROCESS_ENTER, validator);

    DoSetSize( pos.x, pos.y, width, height );
    SetBestSize(wxSize(width, height));
    
    m_min = min;
    m_max = max;
    m_value = initial;
    m_default_value = initial;
    m_increment = increment;
    SetDigits( digits );

    // set the value here without generating an event
    if (!value.IsEmpty()) 
        m_textCtrl->SetValue(value);
    else 
        m_textCtrl->SetValue(wxString::Format(m_textFormat.c_str(), initial));

    return true;
}

wxSpinCtrlDbl::~wxSpinCtrlDbl()
{
    if (m_textCtrl) // null this since MSW sends KILL_FOCUS on deletion 
    {
        m_textCtrl->m_parent = NULL;

        wxSpinCtrlDblTextCtrl *text = m_textCtrl;
        m_textCtrl = NULL;
        delete text;
    }

    delete m_spinButton;
    m_spinButton = NULL;
}

#define wxSPINCTRLDBL_SPIN_WIDTH  15
#define wxSPINCTRLDBL_SPIN_HEIGHT 22

void wxSpinCtrlDbl::DoSetSize(int x, int y, int width, int height, int sizeFlags)
{
    //wxPrintf(wxT("DoSetSize %d, %d %d %d %d %d\n"), GetId(), x, y, width, height, sizeFlags);
    
    wxSize bestSize( DoGetBestSize() );
    if (width < 0)  width  = bestSize.GetWidth();
    if (height < 0) height = bestSize.GetHeight();

    wxWindow::DoSetSize(x, y, width, height, sizeFlags);

    int spinwidth  = wxSPINCTRLDBL_SPIN_WIDTH;
    int spinheight = wxSPINCTRLDBL_SPIN_HEIGHT;
    if (m_spinButton) 
        m_spinButton->GetSize( &spinwidth, &spinheight );

#ifdef __WIN95__   // humm... these used to be different
    if (m_textCtrl)   m_textCtrl->SetSize( 0, 0, width - spinwidth, height );
    if (m_spinButton) m_spinButton->SetSize( width-spinwidth-2, 0, -1, height );
    //m_textCtrl->SetSize( -3, -3, width - spinwidth, height );   // old wxWin < 2.3.2
    //m_spinButton->SetSize( width-spinwidth-4, -3, -1, height-1 );
#else
    if (m_textCtrl)   m_textCtrl->SetSize( 0, 0, width - spinwidth, height );
    if (m_spinButton) m_spinButton->SetSize( width-spinwidth, 0, -1, height );
#endif
}

static wxSize s_spinctrl_bestSize(-999,-999);

wxSize wxSpinCtrlDbl::DoGetBestSize() const
{   
    //wxPrintf(wxT("GetBestSize %d\n"), GetId());
    if (s_spinctrl_bestSize.x == -999)
    {
        wxSpinCtrl spin((wxWindow*)this, wxID_ANY);
        s_spinctrl_bestSize = spin.GetBestSize();
        // oops something went wrong, set to reasonable value
        if (s_spinctrl_bestSize.GetWidth()  < 20) 
            s_spinctrl_bestSize.SetWidth(95);
        if (s_spinctrl_bestSize.GetHeight() < 10) 
            s_spinctrl_bestSize.SetHeight(wxSPINCTRLDBL_SPIN_HEIGHT);
    }

    return s_spinctrl_bestSize;
}

void wxSpinCtrlDbl::DoSendEvent()
{
    wxCommandEvent event( wxEVT_COMMAND_SPINCTRL_UPDATED, GetId() );
    event.SetEventObject( this );
    event.SetInt( (int)(m_value+0.5) );
    if (m_textCtrl) event.SetString( m_textCtrl->GetValue() );
    GetEventHandler()->ProcessEvent( event );
}

void wxSpinCtrlDbl::OnSpinUp( wxSpinEvent &WXUNUSED(event) )
{
    if (m_textCtrl && m_textCtrl->IsModified() ) 
        SyncSpinToText(false);
    
    if ( InRange(m_value + m_increment) )
    {
        m_value += m_increment;
        SetValue( m_value );
        DoSendEvent();
    }
}

void wxSpinCtrlDbl::OnSpinDown( wxSpinEvent &WXUNUSED(event) )
{
    if (m_textCtrl && m_textCtrl->IsModified() ) 
        SyncSpinToText(false);
    
    if ( InRange(m_value - m_increment) )
    {
        m_value -= m_increment;
        SetValue( m_value );
        DoSendEvent();
    }
}

void wxSpinCtrlDbl::OnTextEnter( wxCommandEvent &event )
{
    SyncSpinToText(true);
    event.Skip();
}

void wxSpinCtrlDbl::OnText( wxCommandEvent &event )
{
    //wxPrintf(wxT("Text '%s'\n"), event.GetString()); fflush(stdout);
    event.Skip();
}

void wxSpinCtrlDbl::OnChar( wxKeyEvent &event )
{
    double modifier = 1.0;
    if( event.m_shiftDown )
	{
		modifier  = 2.0;
	}
    if( event.m_controlDown )
	{
		modifier *= 10.0;
	}
    if( event.m_altDown )
	{
		modifier *= 100.0;
	}

    switch( event.GetKeyCode() )
    {
        case WXK_UP :
        {
            if( m_textCtrl && m_textCtrl->IsModified() )
			{
				SyncSpinToText( false );
			}
            SetValue( m_value + m_increment * modifier );
            DoSendEvent();
            break;
        }
        case WXK_DOWN:
        {
            if( m_textCtrl && m_textCtrl->IsModified() )
			{
				SyncSpinToText( false );
			}
            SetValue( m_value - m_increment * modifier );
            DoSendEvent();
            break;
        }
        case WXK_PRIOR:  // pg-up
        {
            if( m_textCtrl && m_textCtrl->IsModified() )
			{
				SyncSpinToText( false );
			}
            SetValue( m_value + m_increment * 10.0 * modifier );
            DoSendEvent();
            break;
        }
        case WXK_NEXT:  // pg-down
        {
            if( m_textCtrl && m_textCtrl->IsModified() ) 
			{
				SyncSpinToText( false );
			}
            SetValue( m_value - m_increment * 10.0 * modifier );
            DoSendEvent();
            break;
        }
		case WXK_RETURN:
        {
			if( m_textCtrl && m_textCtrl->IsModified() )
			{
				SyncSpinToText( false );
			}
            SetValue( m_value );
			DoSendEvent();
            break;
        }
        case WXK_ESCAPE :
        {
            SetDefaultValue();
            DoSendEvent();
            break;
        }
        case WXK_TAB :
        {
            wxNavigationKeyEvent new_event;
            new_event.SetEventObject( GetParent() );
            new_event.SetDirection( !event.ShiftDown() );
            // CTRL-TAB changes the (parent) window, i.e. switch notebook page
            new_event.SetWindowChange( event.ControlDown() );
            new_event.SetCurrentFocus( this );
            GetParent()->GetEventHandler()->ProcessEvent( new_event );
            break;
        }
        default:
			event.Skip();
			break;
    }
}

void wxSpinCtrlDbl::SetValue( double value )
{
    if (!m_textCtrl || !InRange(value))
        return;

    if ( m_snap_ticks && (m_increment != 0) )
    {
        double snap_value = (value - m_default_value) / m_increment;

        if (wxFinite(snap_value)) // FIXME what to do about a failure?
        {
            if (snap_value - floor(snap_value) < ceil(snap_value) - snap_value)
                value = m_default_value + floor(snap_value) * m_increment;
            else
                value = m_default_value + ceil(snap_value) * m_increment;
        }
    }
    
    wxString str(wxString::Format(m_textFormat.c_str(), value));
    
    if ((value != m_value) || (str != m_textCtrl->GetValue()))
    {
        m_textCtrl->SetValue( str );
        m_textCtrl->DiscardEdits();
        m_value = value;
        str.ToDouble( &m_value );    // wysiwyg for textctrl
    }
}

void wxSpinCtrlDbl::SetValue( const wxString& text, bool force )
{
    if (!m_textCtrl) return;

    double value;
    if ( text.ToDouble(&value) ) 
        SetValue( value );
    else if (force)
    {
        m_textCtrl->SetValue( text );
        m_textCtrl->DiscardEdits();
    }
}

void wxSpinCtrlDbl::SetRange( double min_val, double max_val )
{
    //wxCHECK_RET(max_val > min_val, wxT("invalid spinctrl range"));
    m_min = min_val;
    m_max = max_val;

    if (HasRange())
    {
        if (m_value > m_max) 
            SetValue(m_max);
        else if (m_value < m_min) 
            SetValue(m_min);
    }
}

void wxSpinCtrlDbl::SetIncrement( double increment )
{
    m_increment = increment;
    SetValue(m_value);
}

void wxSpinCtrlDbl::SetDigits( int digits, formatType fmt )
{
    wxCHECK_RET(digits >= -1, wxT("invalid spinctrl format"));

    if ((digits == wxSPINCTRLDBL_AUTODIGITS) && (fmt != lg_fmt))
    {
        wxString wxstr;
        int lastplace = -1, extra_digits = 0;
        if (fmt == le_fmt)
        {
            wxstr.Printf(wxT("%le"), m_increment );
            wxstr.LowerCase();
            lastplace = wxstr.Find(wxT('e')) - 2;
            long places;
            if (wxstr.AfterFirst(wxT('e')).ToLong(&places))
                extra_digits = int(labs(places));
        }
        else if (fmt == lf_fmt)
        {
            wxstr.Printf(wxT("%lf"), m_increment );
            lastplace = wxstr.Len()-1;
        }

        int decimalplace = wxstr.Find(wxT('.'));

        int i = 0;

        for ( i=lastplace; i>decimalplace; i-- )
        {
            if ( wxstr.GetChar(i) != wxT('0') )
            {
                m_digits = extra_digits + i-decimalplace;
                switch (fmt)
                {
                    case le_fmt : m_textFormat.Printf(wxT("%%.%dle"), m_digits ); break;
                    case lf_fmt :
                    default     : m_textFormat.Printf(wxT("%%.%dlg"), m_digits ); break;
                }

                SetValue(m_value);
                return;
            }
        }

        m_digits = 0;  // no digits, I guess
    }
    else
        m_digits = digits;

    switch (fmt)
    {
        case le_fmt : m_textFormat.Printf(wxT("%%.%dle"), m_digits ); break;
        case lg_fmt : 
        {
            if (m_digits == -1)
                m_textFormat.Printf(wxT("%%lg") ); 
            else 
                m_textFormat.Printf(wxT("%%.%dlg"), m_digits ); 
            break;
        }
        case lf_fmt :
        default     : m_textFormat.Printf(wxT("%%.%dlf"), m_digits ); break;
    }

    SetValue(m_value);
}

void wxSpinCtrlDbl::SetFormat( const wxString& format )
{
    wxString wxstr;
    if ( wxstr.Printf(format.c_str(), 123456.123456) > 0 ) 
        m_textFormat = format;
    
    SetValue(m_value);
}

void wxSpinCtrlDbl::SetDefaultValue( double default_value )
{
    if ( InRange(default_value) )
    {
        m_default_value = default_value;
        SetDefaultValue();
    }
}

void wxSpinCtrlDbl::SetSnapToTicks(bool forceTicks)
{
    if (m_snap_ticks != forceTicks)
    {
        m_snap_ticks = forceTicks;
        SetValue( m_value );
    }
}

void wxSpinCtrlDbl::OnFocus( wxFocusEvent &event )
{
    if (m_textCtrl)
	{
		m_default_value = m_value;
        m_textCtrl->SetFocus(); // this is to pass TAB navigation
	}
    event.Skip();
}

void wxSpinCtrlDbl::OnKillFocus( wxFocusEvent &event )
{
    SyncSpinToText(true);
    event.Skip();
}

void wxSpinCtrlDbl::SyncSpinToText(bool send_event, bool force_valid)
{
    if (!m_textCtrl)
        return;

    double txt_value;
	// ToDouble returns false for strings like '0.45 dB' even if value after this
	// call contains the correct data. This we don't want as our strings might contain
	// unit information. Therefore the return value check has been removed here.
    //if ( m_textCtrl->GetValue().ToDouble( &txt_value ) )
	m_textCtrl->GetValue().ToDouble( &txt_value );
    //{
        if ( force_valid || !HasRange() || InRange(txt_value) )
        {
            if (force_valid && HasRange())
            {
                if (txt_value > GetMax()) 
                    txt_value = GetMax();
                else if (txt_value < GetMin()) 
                    txt_value = GetMin();
            }

            if (m_value != txt_value)
            {
                SetValue( txt_value );
                if (send_event) DoSendEvent();
            }
        }
    //}
    //else if (force_valid)
    //{  
    //    // textctrl is out of sync, discard and reset
    //    SetValue(GetValue());
    //}
}

bool wxSpinCtrlDbl::SetFont( const wxFont &font )
{
    if (!m_textCtrl) return false;
    return m_textCtrl->SetFont( font );
}
wxFont wxSpinCtrlDbl::GetFont() const
{
    if (!m_textCtrl) return GetFont();
    return m_textCtrl->GetFont();
}

