/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2016 CERN * Copyright (C) 2022-2023 KiCad Developers, see AUTHORS.txt for contributors. * * @author Maciej Suminski * * 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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, you may find one here: * https://www.gnu.org/licenses/gpl-3.0.html * or you may search the http://www.gnu.org website for the version 3 license, * or you may write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #include #include #include #include #include #include #include // log log1p expm1 #include // norm // Must be after other includes to avoid conflict with a window header on msys2 #include "tuner_slider.h" #include "core/kicad_algo.h" TUNER_SLIDER::TUNER_SLIDER( SIMULATOR_FRAME_UI* aFrame, wxWindow* aParent, const SCH_SHEET_PATH& aSheetPath, SCH_SYMBOL* aSymbol ) : TUNER_SLIDER_BASE( aParent ), m_symbol( aSymbol->m_Uuid ), m_sheetPath( aSheetPath ), m_ref( aSymbol->GetRef( &aSheetPath ) ), m_min( 0.0 ), m_max( 0.0 ), m_value( 0.0 ), m_frame( aFrame ) { const SPICE_ITEM* item = m_frame->GetExporter()->FindItem( std::string( m_ref.ToUTF8() ) ); if( !item ) throw KI_PARAM_ERROR( wxString::Format( _( "%s not found" ), m_ref ) ); m_name->SetLabel( wxString::Format( _( "Tune %s" ), m_ref ) ); m_closeBtn->SetBitmap( KiBitmapBundle( BITMAPS::small_trash ) ); m_e24->SetBitmap( KiBitmapBundle( BITMAPS::e_24 ) ); m_e24->SetIsCheckButton(); m_separator->SetIsSeparator(); m_e48->SetBitmap( KiBitmapBundle( BITMAPS::e_48 ) ); m_e48->SetIsCheckButton(); m_e96->SetBitmap( KiBitmapBundle( BITMAPS::e_96 ) ); m_e96->SetIsCheckButton(); m_e192->SetBitmap( KiBitmapBundle( BITMAPS::e_192 ) ); m_e192->SetIsCheckButton(); const SIM_MODEL::PARAM* tunerParam = item->model->GetTunerParam(); if( !tunerParam ) { throw KI_PARAM_ERROR( wxString::Format( _( "%s has simulation model of type '%s %s'; " "only RLC passives be tuned" ), m_ref, item->model->GetDeviceInfo().fieldValue, item->model->GetTypeInfo().fieldValue ) ); } // Special case for potentiometers because we don't have value ranges implemented yet. if( item->model->GetType() == SIM_MODEL::TYPE::R_POT ) { std::string valueStr = SIM_VALUE::ToSpice( item->model->GetTunerParam()->value ); if( valueStr != "" ) m_value = SPICE_VALUE( valueStr ); else m_value = SPICE_VALUE( "0.5" ); m_min = SPICE_VALUE( 0 ); m_max = SPICE_VALUE( 1 ); } else { m_value = SPICE_VALUE( SIM_VALUE::ToSpice( item->model->GetTunerParam()->value ) ); m_min = SPICE_VALUE( 0.5 ) * m_value; m_max = SPICE_VALUE( 2.0 ) * m_value; } m_minText->SetValue( m_min.ToOrigString() ); m_maxText->SetValue( m_max.ToOrigString() ); updateValueText(); updateSlider(); Layout(); } void TUNER_SLIDER::ShowChangedLanguage() { m_name->SetLabel( wxString::Format( _( "Tune %s" ), m_ref ) ); } void TUNER_SLIDER::onESeries( wxCommandEvent& event ) { if( event.GetEventObject() != m_e24 ) { for( BITMAP_BUTTON* btn : { m_e48, m_e96, m_e192 } ) { if( btn != event.GetEventObject() ) btn->Check( false ); } } wxString oldValue = m_valueText->GetValue(); updateValueText(); if( m_valueText->GetValue() != oldValue ) updateComponentValue(); event.Skip(); } bool TUNER_SLIDER::SetValue( const SPICE_VALUE& aVal ) { // Get the value into the current range boundaries if( aVal > m_max ) m_value = m_max; else if( aVal < m_min ) m_value = m_min; else m_value = aVal; updateValueText(); updateSlider(); updateComponentValue(); return true; } bool TUNER_SLIDER::SetMin( const SPICE_VALUE& aVal ) { if( aVal >= m_max ) return false; m_min = aVal; if( m_value < aVal ) // Limit the current value SetValue( aVal ); m_minText->SetValue( aVal.ToOrigString() ); updateSlider(); return true; } bool TUNER_SLIDER::SetMax( const SPICE_VALUE& aVal ) { if( aVal <= m_min ) return false; m_max = aVal; if( m_value > aVal ) // Limit the current value SetValue( aVal ); m_maxText->SetValue( aVal.ToOrigString() ); updateSlider(); return true; } void TUNER_SLIDER::updateComponentValue() { wxQueueEvent( m_frame, new wxCommandEvent( EVT_SIM_UPDATE ) ); } void TUNER_SLIDER::updateSlider() { wxASSERT( m_max >= m_value && m_value >= m_min ); double value = ( ( m_value - m_min ) / ( m_max - m_min ) ).ToDouble(); m_slider->SetValue( KiROUND( value * 100.0 ) ); } void TUNER_SLIDER::updateValueText() { static std::vector e24 = { 1.0, 1.1, 1.2, 1.3, 1.5, 1.6, 1.8, 2.0, 2.2, 2.4, 2.7, 3.0, 3.3, 3.6, 3.9, 4.3, 4.7, 5.1, 5.6, 6.2, 6.8, 7.5, 8.2, 9.1 }; static std::vector e192 = { 1.00, 1.01, 1.02, 1.04, 1.05, 1.06, 1.07, 1.09, 1.10, 1.11, 1.13, 1.14, 1.15, 1.17, 1.18, 1.20, 1.21, 1.23, 1.24, 1.26, 1.27, 1.29, 1.30, 1.32, 1.33, 1.35, 1.37, 1.38, 1.40, 1.42, 1.43, 1.45, 1.47, 1.49, 1.50, 1.52, 1.54, 1.56, 1.58, 1.60, 1.62, 1.64, 1.65, 1.67, 1.69, 1.72, 1.74, 1.76, 1.78, 1.80, 1.82, 1.84, 1.87, 1.89, 1.91, 1.93, 1.96, 1.98, 2.00, 2.03, 2.05, 2.08, 2.10, 2.13, 2.15, 2.18, 2.21, 2.23, 2.26, 2.29, 2.32, 2.34, 2.37, 2.40, 2.43, 2.46, 2.49, 2.52, 2.55, 2.58, 2.61, 2.64, 2.67, 2.71, 2.74, 2.77, 2.80, 2.84, 2.87, 2.91, 2.94, 2.98, 3.01, 3.05, 3.09, 3.12, 3.16, 3.20, 3.24, 3.28, 3.32, 3.36, 3.40, 3.44, 3.48, 3.52, 3.57, 3.61, 3.65, 3.70, 3.74, 3.79, 3.83, 3.88, 3.92, 3.97, 4.02, 4.07, 4.12, 4.17, 4.22, 4.27, 4.32, 4.37, 4.42, 4.48, 4.53, 4.59, 4.64, 4.70, 4.75, 4.81, 4.87, 4.93, 4.99, 5.05, 5.11, 5.17, 5.23, 5.30, 5.36, 5.42, 5.49, 5.56, 5.62, 5.69, 5.76, 5.83, 5.90, 5.97, 6.04, 6.12, 6.19, 6.26, 6.34, 6.42, 6.49, 6.57, 6.65, 6.73, 6.81, 6.90, 6.98, 7.06, 7.15, 7.23, 7.32, 7.41, 7.50, 7.59, 7.68, 7.77, 7.87, 7.96, 8.06, 8.16, 8.25, 8.35, 8.45, 8.56, 8.66, 8.76, 8.87, 8.98, 9.09, 9.20, 9.31, 9.42, 9.53, 9.65, 9.76, 9.88 }; int precision = 3; wxString prefix; double value = m_value.ToNormalizedDouble( &prefix ); bool e_24 = m_e24->IsChecked(); bool e_extended = m_e48->IsChecked() || m_e96->IsChecked() || m_e192->IsChecked(); if( e_24 || e_extended ) { std::vector table; table.reserve( 192 + 24 + 1 /* worst case */ ); if( e_extended ) { int step = m_e48->IsChecked() ? 4 : m_e96->IsChecked() ? 2 : 1; for( size_t ii = 0; ii < e192.size(); ii += step ) table.push_back( e192[ii] ); } if( e_24 ) table.insert( table.end(), e24.begin(), e24.end() ); table.push_back( 10.0 ); std::sort( table.begin(), table.end() ); alg::remove_duplicates( table ); for( double decade : { 1.0, 10.0, 100.0 } ) { for( size_t ii = 0; ii < table.size() - 1; ++ii ) { if( value < ( table[ii] + table[ii+1] ) * decade / 2 ) { precision = 0; if( decade == 1.0 ) precision++; if( e_extended && decade != 100.0 ) precision++; m_valueText->SetValue( wxString::Format( wxT( "%.*f%s" ), precision, table[ii] * decade, prefix ) ); return; } } } } wxString valueStr = wxString::Format( wxT( "%.3f" ), value ); SPICE_VALUE::StripZeros( valueStr ); m_valueText->SetValue( valueStr + prefix ); } void TUNER_SLIDER::updateMax() { try { SPICE_VALUE newMax( m_maxText->GetValue() ); SetMax( newMax ); } catch( const KI_PARAM_ERROR& ) { // Restore the previous value m_maxText->SetValue( m_max.ToOrigString() ); } } void TUNER_SLIDER::updateValue() { try { SPICE_VALUE newCur( m_valueText->GetValue() ); SetValue( newCur ); } catch( const KI_PARAM_ERROR& ) { // Restore the previous value m_valueText->SetValue( m_value.ToOrigString() ); } } void TUNER_SLIDER::updateMin() { try { SPICE_VALUE newMin( m_minText->GetValue() ); SetMin( newMin ); } catch( const KI_PARAM_ERROR& ) { // Restore the previous value m_minText->SetValue( m_min.ToOrigString() ); } } void TUNER_SLIDER::onClose( wxCommandEvent& event ) { m_frame->RemoveTuner( this ); } void TUNER_SLIDER::onSave( wxCommandEvent& event ) { m_frame->UpdateTunerValue( m_sheetPath, m_symbol, GetSymbolRef(), m_value.ToOrigString() ); } void TUNER_SLIDER::onSliderScroll( wxScrollEvent& event ) { m_value = m_min + ( m_max - m_min ) * SPICE_VALUE( m_slider->GetValue() / 100.0 ); updateValueText(); } void TUNER_SLIDER::onSliderChanged( wxScrollEvent& event ) { m_value = m_min + ( m_max - m_min ) * SPICE_VALUE( m_slider->GetValue() / 100.0 ); updateValueText(); updateComponentValue(); } void TUNER_SLIDER::onMaxKillFocus( wxFocusEvent& event ) { updateMax(); event.Skip(); // Mandatory in wxFocusEvent } void TUNER_SLIDER::onValueKillFocus( wxFocusEvent& event ) { updateValue(); event.Skip(); // Mandatory in wxFocusEvent } void TUNER_SLIDER::onMinKillFocus( wxFocusEvent& event ) { updateMin(); event.Skip(); // Mandatory in wxFocusEvent } void TUNER_SLIDER::onMaxTextEnter( wxCommandEvent& event ) { updateMax(); event.Skip(); // Mandatory in wxFocusEvent } void TUNER_SLIDER::onValueTextEnter( wxCommandEvent& event ) { updateValue(); } void TUNER_SLIDER::onMinTextEnter( wxCommandEvent& event ) { updateMin(); }