//**************************************************************************************************
//                                         PnlTxtSpn.cpp                                           *
//                                        ---------------                                          *
// Started     : 2004-03-20                                                                        *
// Last Update : 2018-10-16                                                                        *
// Copyright   : (C) 2004-2018 MSWaters                                                            *
//**************************************************************************************************

//**************************************************************************************************
//                                                                                                 *
//      This program is free software; you can redistribute it and/or modify it under the          *
//      terms of the GNU General Public License as published by the Free Software Foundation;      *
//      either version 3 of the License, or (at your option) any later version.                    *
//                                                                                                 *
//**************************************************************************************************

#include "PnlTxtSpn.hpp"

//**************************************************************************************************
// Allocate storage for static data members.

uint  PnlTxtSpn::m_uiSpnPeriod = SPN_PERIOD_DEF;

//**************************************************************************************************
// Implement an event table.

wxBEGIN_EVENT_TABLE( PnlTxtSpn, wxPanel )

  EVT_CHAR       (                        PnlTxtSpn::OnTxtChar   )

  EVT_TEXT_MAXLEN( PnlTxtSpn::ID_TXTCTRL, PnlTxtSpn::OnTxtMaxLen )

  EVT_SPIN_UP    ( PnlTxtSpn::ID_SPINBTN, PnlTxtSpn::OnSbnInc    )
  EVT_SPIN_DOWN  ( PnlTxtSpn::ID_SPINBTN, PnlTxtSpn::OnSbnDec    )

wxEND_EVENT_TABLE( )

//**************************************************************************************************
// Constructor.

PnlTxtSpn::PnlTxtSpn( void ) : wxPanel( )
{
  // m_eValType must be initialized before calling bClear( ) or valgrind complains that it's value
  // is tested in bClear( ) before being initialized (the problem is there but is benign)
  m_eValType = eVALUE_INT;

  bClear( );
}

//**************************************************************************************************
// Destructor.

PnlTxtSpn::~PnlTxtSpn( )
{
}

//**************************************************************************************************
// Layout the display objects.

void  PnlTxtSpn::DoLayout( void )
{
  wxBoxSizer * poSzr;
  wxSizerFlags  oFlags;

  // Create and set the sizer for the panel
  poSzr = new wxBoxSizer( wxHORIZONTAL );
  SetSizer( poSzr );

#if !wxCHECK_VERSION( 3,0,0 )
  oFlags.Expand( );
#endif

  // Add the text control
  oFlags.Proportion( 1 );
  poSzr->Add( &m_oTxtValue, oFlags );

  // Add the spin control
  oFlags.Proportion( 0 );
  poSzr->Add( &m_oSbnValue, oFlags );

  // Set the panel sizer and the min. & init. sizes as calculated by the sizer
  poSzr->SetSizeHints( this );
}

//**************************************************************************************************
// Create an instance of this object.
//
// Argument List :
//   poWin  - The parent window
//   oWinID - The window identifier
//   iWidth - The width the spin control in pixels
//
// Return Values :
//   true  - Success
//   false - Failure

bool  PnlTxtSpn::bCreate( wxWindow * poWin, wxWindowID oWinID, int iWidth )
{
  int  i1;

  if( bIsCreated( ) )                      return( true );

  // Subtract 15 (pixels) from iWidth to allow for the spin control width
  i1 = iWidth - 15;
  if( i1 <= 0 ) i1 = -1;

  // Create the base class (wxPanel)
  if( ! wxPanel::Create( poWin, oWinID ) ) return( false );

  // Create the text control
  m_oTxtValue.Create( this, ID_TXTCTRL, wxT(""), wxDefaultPosition, wxSize( i1, -1 ),
                      wxTE_RIGHT );

  // Create the spin control
  m_oSbnValue.Create( this, ID_SPINBTN,          wxDefaultPosition, wxDefaultSize,
                      wxSP_VERTICAL | wxSP_ARROW_KEYS );

  // Set the display object attributes
  m_oTxtValue.SetMaxLength( SPN_MAXLEN );
  m_oSbnValue.SetRange( -0x8000, 0x7FFF );
  bSetValDef( m_dfDefValue );
  bSetRange( m_dfMinValue, m_dfMaxValue );
  bSetIncSz( m_dfMinIncSz, m_dfMaxIncSz );
  bSetValue( m_dfDefValue );

  // Layout the display objects
  DoLayout( );

  return( true );
}

//**************************************************************************************************
// Clear the object attributes.
//
// Return Values :
//   true  - Success
//   false - Failure

bool  PnlTxtSpn::bClear( void )
{
  bool  bRtn=true;

  if( ! bSetValType( eVALUE_FLT )      ) bRtn = false;
  if( ! bSetRange( -10000.0, 10000.0 ) ) bRtn = false;  // bSetRange( ) must preceed bSetValDef( )
  if( ! bSetValDef( 0.0 )              ) bRtn = false;
  if( ! bSetIncSz( 1.0, 100.0 )        ) bRtn = false;

  if( m_oTxtValue.GetParent( ) != NULL ) m_oTxtValue.Clear( );

  return( bRtn );
}

//**************************************************************************************************
// Set the variable type to be displayed by the spin control.
//
// Argument List :
//   eVType - The variable type specifier
//
// Return Values :
//   true  - Success
//   false - Failure

bool  PnlTxtSpn::bSetValType( eTypeValue eVType )
{
  double df1;

  // Argument validity checks
  if( m_eValType == eVType )                        return( true );
  if( eVType!=eVALUE_INT && eVType!=eVALUE_FLT &&
      eVType!=eVALUE_SCI && eVType!=eVALUE_ENG    ) return( false );

  // Set the new variable type
  m_eValType = eVType;

  // Display the new variable type
  if( bIsCreated( ) )
  {
    if( ! rosGetValue( ).IsEmpty( ) )
    { // Attempt to display the current value using the new format
      df1 = dfGetValue( );
      bSetValue( df1 );
    }
  }

  return( true );
}

//**************************************************************************************************
// Set the current value of the spin control.
//
// Argument List :
//   dfValue - The float value
//
// Return Values :
//   true  - Success
//   false - Failure

bool  PnlTxtSpn::bSetValue( double dfValue )
{
  wxString  osValue;
  double    df1;
  long      li1;
  int       i1;

  // Argument validity checks
  if( ! bIsCreated( ) )                                                           return( false );
  if( dfValue<m_dfMinValue && !CnvtType::bIsEqual( dfValue, m_dfMinValue ) )
    if( ! ((m_eValType==eVALUE_SCI || m_eValType==eVALUE_ENG) && dfValue==0.0 ) ) return( false );
  if( dfValue>m_dfMaxValue && !CnvtType::bIsEqual( dfValue, m_dfMaxValue ) )      return( false );

  // Format the string to load into the control
  switch( m_eValType )
  {
    case eVALUE_HEX :
      if( dfValue < 0.0 )                                                         return( false );
      li1 = lround( dfValue );
      osValue.Printf( wxT("%X"), (ulong) li1 );
      break;

    case eVALUE_INT :
      li1 = lround( dfValue );
      osValue.Printf( wxT("%li"), li1 );
      break;

    case eVALUE_FLT :
      osValue.Printf( wxT("%#.2f"), dfValue );
      break;

    case eVALUE_SCI :
      osValue.Printf( wxT("%#.2E"), dfValue );
      break;

    case eVALUE_ENG :
      if( ! CnvtType::bParseFlt( dfValue, &df1, &i1 ) )
        return( false );
      for( ; i1%3!=0; i1-- ) df1 *= 10.0;
      osValue.Printf( wxT("%#.2fE%02i"), df1, i1 );
      break;

    default:                                                                      return( false );
  }

  // Load the new value
  osValue.Append( wxT(" ") );
  m_oTxtValue.SetValue( osValue );

  return( true );
}

//**************************************************************************************************
// Set the current value of the spin control.
//
// Argument List :
//   rosValue - The string value
//
// Return Values :
//   true  - Success
//   false - Failure

bool  PnlTxtSpn::bSetValue( const wxString & rosValue )
{
  double  dfValue;

  if( ! CnvtType::bStrToFlt( rosValue, &dfValue ) ) return( false );
  if( ! bSetValue( dfValue ) )                      return( false );

  return( true );
}

//**************************************************************************************************
// Set the initial value of the spin control.
//
// Argument List :
//   dfDefValue - The spin control default value
//
// Return Values :
//   true  - Success
//   false - Failure

bool  PnlTxtSpn::bSetValDef( double dfDefValue )
{
  if( dfDefValue == NOVAL_DBL ) return( bSetValue( m_dfDefValue ) );

  if( dfDefValue<m_dfMinValue && !CnvtType::bIsEqual( dfDefValue, m_dfMinValue ) ) return( false );
  if( dfDefValue>m_dfMaxValue && !CnvtType::bIsEqual( dfDefValue, m_dfMaxValue ) ) return( false );

  m_dfDefValue = dfDefValue;

  return( true );
}

//**************************************************************************************************
// Set the increment sizes of the spin control.
//
// This spin control can be incremented using two different approaches,
// constant or variable step sizes. A constant step size means that the spin
// control is incremented by the same amount throughout it's range. A variable
// step size means that the spin control is incremented by an amount dependent
// on it's current value.
//
// Argument List :
//   dfMinIncSz - The minimum spin control increment size
//   dfMaxIncSz - The maximum spin control increment size
//
// Return Values :
//   true  - Success
//   false - Failure

bool  PnlTxtSpn::bSetIncSz( double dfMinIncSz, double dfMaxIncSz )
{
  // Constant or variable incrementing?
  if( dfMinIncSz < 0.0 )                           return( false );
  if( dfMaxIncSz < 0.0 ) dfMaxIncSz = dfMinIncSz;

  // Do some validity checks on the arguments
  if( dfMinIncSz > dfMaxIncSz )                    return( false );
  // Check that the new increment sizes fit within the current range
  if( dfMaxIncSz > (m_dfMaxValue - m_dfMinValue) ) return( false );

  m_dfMinIncSz = dfMinIncSz; // Set increment sizes
  m_dfMaxIncSz = dfMaxIncSz;

  return( true );
}

//**************************************************************************************************
// Set the spin control range.
//
// Argument List :
//   dfMinValue - The spin control minimum value
//   dfMaxValue - The spin control maximum value
//
// Return Values :
//   true  - Success
//   false - Failure

bool  PnlTxtSpn::bSetRange( double dfMinValue, double dfMaxValue )
{
  wxString  os1;
  double    df1;

  // Check argument validity
  if( dfMinValue >= dfMaxValue ) return( false );

  // Set new parameter values
  m_dfMinValue = dfMinValue;
  m_dfMaxValue = dfMaxValue;

  // Update the text control if necessary
  if( bIsCreated( ) )
  {
    os1 = m_oTxtValue.GetValue( );
    if( CnvtType::bStrToFlt( os1, &df1 ) )
    {
      if(      df1 < m_dfMinValue ) bSetValue( m_dfMinValue );
      else if( df1 > m_dfMaxValue ) bSetValue( m_dfMaxValue );
    }
  }

  return( true );
}

//**************************************************************************************************
// Get the current spin control value as an integer.
//
// Return Values :
//   Success - The integer value
//   Failure - NOVAL_LONG

long  PnlTxtSpn::liGetValue( void )
{
  double  df1;
  long    li1;

  // Get the value as a float
  df1 = dfGetValue( );
  if( df1 == NOVAL_DBL ) return( NOVAL_LNG );

  // Round the value
  li1 = lround( df1 );

  return( li1 );
}

//**************************************************************************************************
// Get the current spin control value as a float.
//
// Return Values :
//   Success - The float value
//   Failure - NOVAL_DBL

double  PnlTxtSpn::dfGetValue( void )
{
  wxString  os1;
  double    df1;

  // Has the control been created?
  if( ! bIsCreated( ) )                    return( NOVAL_DBL );

  // Get the value as a float
  os1 = m_oTxtValue.GetValue( );
  if( ! CnvtType::bStrToFlt( os1, &df1 ) ) return( NOVAL_DBL );

  return( df1 );
}

//**************************************************************************************************
// Get the current spin control value as a string.
//
// Return Values :
//   Success - The string value
//   Failure - An empty string

const wxString & PnlTxtSpn::rosGetValue( void )
{
  static  wxString  osValue;

  osValue.Empty( );

  if( dfGetValue( ) != NOVAL_DBL )
  {
    osValue = m_oTxtValue.GetValue( );
    osValue.Trim( );
  }

  return( osValue );
}

//**************************************************************************************************
// Set the spin control repeat rate.
//
// Argument List :
//   uiPeriod - The period between successive spin control updates (in mSec)
//
// Return Values :
//   Success - true
//   Failure - false

bool  PnlTxtSpn::bSetSpnPeriod( uint uiPeriod )
{
  if( uiPeriod<SPN_PERIOD_MIN || uiPeriod>SPN_PERIOD_MAX ) return( false );

  m_uiSpnPeriod = uiPeriod;

  return( true );
}

//**************************************************************************************************
// Print the object attributes.
//
// Argument List :
//   rosPrefix - A prefix to every line displayed (usually just spaces)

void  PnlTxtSpn::Print( const wxString & rosPrefix )
{
  std::cout << rosPrefix.mb_str( ) << "m_eValType   : ";
  switch( m_eValType )
  {
    case eVALUE_BIN  : std::cout << "eVALUE_BIN";  break;
    case eVALUE_OCT  : std::cout << "eVALUE_OCT";  break;
    case eVALUE_HEX  : std::cout << "eVALUE_HEX";  break;
    case eVALUE_INT  : std::cout << "eVALUE_INT";  break;
    case eVALUE_FLT  : std::cout << "eVALUE_FLT";  break;
    case eVALUE_SCI  : std::cout << "eVALUE_SCI";  break;
    case eVALUE_ENG  : std::cout << "eVALUE_ENG";  break;
    case eVALUE_NONE : std::cout << "eVALUE_NONE"; break;

    default          : std::cout << "Invalid";
  }
  std::cout << '\n';

  std::cout << rosPrefix.mb_str( ) << "m_dfDefValue : " << m_dfDefValue << '\n';
  std::cout << rosPrefix.mb_str( ) << "m_dfMinValue : " << m_dfMinValue << '\n';
  std::cout << rosPrefix.mb_str( ) << "m_dfMaxValue : " << m_dfMaxValue << '\n';
  std::cout << rosPrefix.mb_str( ) << "m_dfMinIncSz : " << m_dfMinIncSz << '\n';
  std::cout << rosPrefix.mb_str( ) << "m_dfMaxIncSz : " << m_dfMaxIncSz << '\n';
}

//**************************************************************************************************
//                                         Event Handlers                                          *
//**************************************************************************************************
// Key press event handler.
//
// Argument List :
//   roEvtKey - An object holding information about the event

void  PnlTxtSpn::OnTxtChar( wxKeyEvent & roEvtKey )
{
  wxSpinEvent  oEvtSpn;
  int          iKeyCode;

  // Look for the modifier keys <Ctrl> and <Alt>
  if( roEvtKey.HasModifiers( ) )                         return;

  // Get the key code
  iKeyCode = roEvtKey.GetKeyCode( );

  // Process arrow keys etc.
  if(      iKeyCode == WXK_UP   ) { OnSbnInc( oEvtSpn ); return; }
  else if( iKeyCode == WXK_DOWN ) { OnSbnDec( oEvtSpn ); return; }

  // Test that the key code is valid and reject invalid characters
  // ??? (2007-07-07) This doesn't work but it would be good if it did. Only up
  // and down arrow keys get through to this function, don't know why.
  switch( iKeyCode )
  {
    case (int) '.'  :
    case (int) '0'  :
    case (int) '1'  :
    case (int) '2'  :
    case (int) '3'  :
    case (int) '4'  :
    case (int) '5'  :
    case (int) '6'  :
    case (int) '7'  :
    case (int) '8'  :
    case (int) '9'  :
    case (int) '+'  :
    case (int) '-'  :
    case (int) 'e'  :
    case (int) 'E'  :
    case WXK_BACK   :
    case WXK_DELETE :
      break;

    default :
      wxBell( );  // Sound the system bell
  }
}

//**************************************************************************************************
// Text control maximum length reached event handler.
//
// Argument List :
//   roEvtCmd - An object holding information about the event

void  PnlTxtSpn::OnTxtMaxLen( wxCommandEvent & roEvtCmd )
{
  wxBell( );  // Sound the system bell
}

//**************************************************************************************************
// Spin button scroll up event handler.
//
// Argument List :
//   roEvtSpn - An object holding information about the event

void  PnlTxtSpn::OnSbnInc( wxSpinEvent & roEvtSpn )
{
  static  uint      ui_msec=0;
          double    dfValue, dfIncSz;
          wxString  os1;
          double    df1, df2;
          uint      ui1;
          int       i1;

  // Allow additional event handlers to be called
  roEvtSpn.Skip( );

  // Increment the text control SPN_RPTRATE times per second
  ui1 = (uint) wxGetLocalTimeMillis( ).GetLo( );
  if( ui1>ui_msec && (ui1-ui_msec)<m_uiSpnPeriod ) return;
  ui_msec = ui1;

  // Get the current text control value
  os1 = m_oTxtValue.GetValue( );
  df1 = 0.0;
  if( ! CnvtType::bStrToFlt( os1, &df1 ) )         return;
  if( ! CnvtType::bParseFlt( df1, &df2, &i1 ) )    return;
  dfValue = df1;

  // Determine the increment size to use based on the current value
  if( m_eValType==eVALUE_SCI || m_eValType==eVALUE_ENG )
  {
    dfIncSz = EXP10( (double) i1-1 );
    if( dfValue == 0.0 ) dfIncSz = m_dfMinValue;
  }
  else
  {
    dfIncSz = EXP10( (double) i1 );
    if( CnvtType::bIsEqual( dfIncSz, -dfValue ) ) dfIncSz /= 10.0;
    if( dfIncSz < m_dfMinIncSz ) dfIncSz = m_dfMinIncSz;
    if( dfIncSz > m_dfMaxIncSz ) dfIncSz = m_dfMaxIncSz;
  }

  // Determine if the text control value is a whole multiple of the increment size
  if( ! CnvtType::bIsEqual( fmod( dfValue, dfIncSz ), 0.0 ) )
    dfIncSz = ceil( dfValue / dfIncSz ) * dfIncSz - dfValue;

  // Increment the value and check it against the minimum and maximum limits
  dfValue += dfIncSz;
  if(      dfValue > m_dfMaxValue ) dfValue = m_dfMaxValue;
  else if( dfValue < m_dfMinValue ) dfValue = m_dfMinValue;

  // Set the text control value
  bSetValue( dfValue );
}

//**************************************************************************************************
// Spin button scroll down event handler.
//
// Argument List :
//   roEvtSpn - An object holding information about the event

void  PnlTxtSpn::OnSbnDec( wxSpinEvent & roEvtSpn )
{
  static  uint      ui_msec=0;
          double    dfValue, dfDecSz;
          wxString  os1;
          double    df1, df2;
          uint      ui1;
          int       i1;

  // Allow additional event handlers to be called
  roEvtSpn.Skip( );

  // Decrement the text control SPN_RPTRATE times per second
  ui1 = (uint) wxGetLocalTimeMillis( ).GetLo( );
  if( ui1>ui_msec && (ui1-ui_msec)<m_uiSpnPeriod ) return;
  ui_msec = ui1;

  // Get the current text control value
  os1 = m_oTxtValue.GetValue( );
  df1 = 0.0;
  if( ! CnvtType::bStrToFlt( os1, &df1 ) )         return;
  if( ! CnvtType::bParseFlt( df1, &df2, &i1 ) )    return;
  dfValue = df1;

  // Determine the decrement size to use based on the current value
  if( m_eValType==eVALUE_SCI || m_eValType==eVALUE_ENG )
  {
    dfDecSz = EXP10( (double) i1-1 );
    if( dfValue == 0.0 ) dfDecSz = m_dfMinValue;
  }
  else
  {
    dfDecSz = EXP10( (double) i1 );
    if( CnvtType::bIsEqual( dfDecSz, dfValue ) ) dfDecSz /= 10.0;
    if( dfDecSz < m_dfMinIncSz ) dfDecSz = m_dfMinIncSz;
    if( dfDecSz > m_dfMaxIncSz ) dfDecSz = m_dfMaxIncSz;
  }

  // Determine if the text control value is a whole multiple of the decrement size
  if( ! CnvtType::bIsEqual( round( dfValue / dfDecSz ), dfValue / dfDecSz ) )
    dfDecSz = dfValue - floor( dfValue / dfDecSz ) * dfDecSz;

  // Decrement the value and check it against the minimum and maximum limits
  dfValue -= dfDecSz;
  if(      dfValue < m_dfMinValue )
  {
    // The following makes zero a valid value even if the allowable limits don't include it
    if( m_eValType==eVALUE_SCI || m_eValType==eVALUE_ENG ) dfValue = 0.0;
    else                            dfValue = m_dfMinValue;
  }
  else if( dfValue > m_dfMaxValue ) dfValue = m_dfMaxValue;

  // Set the text control value
  bSetValue( dfValue );
}

//**************************************************************************************************
