kicad/eeschema/dialogs/dialog_spice_model.cpp

624 lines
20 KiB
C++
Raw Normal View History

2016-08-11 12:41:40 +00:00
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2022 Mikolaj Wielgus
* Copyright (C) 2022 KiCad Developers, see AUTHORS.txt for contributors.
2016-08-11 12:41:40 +00:00
*
* 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
2016-08-11 12:41:40 +00:00
* 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,
2016-08-11 12:41:40 +00:00
* or you may write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include <dialog_spice_model.h>
#include <sim/sim_property.h>
#include <widgets/wx_grid.h>
#include <kiplatform/ui.h>
2016-08-11 12:41:56 +00:00
#include <confirm.h>
#include <locale_io.h>
2016-08-11 12:41:40 +00:00
using TYPE = SIM_VALUE_BASE::TYPE;
using CATEGORY = SIM_MODEL::PARAM::CATEGORY;
template class DIALOG_SPICE_MODEL<SCH_FIELD>;
template class DIALOG_SPICE_MODEL<LIB_FIELD>;
template <typename T>
DIALOG_SPICE_MODEL<T>::DIALOG_SPICE_MODEL( wxWindow* aParent, SCH_SYMBOL& aSymbol,
std::vector<T>& aFields )
: DIALOG_SPICE_MODEL_BASE( aParent ),
m_symbol( aSymbol ),
m_fields( aFields ),
m_firstCategory( nullptr )
{
try
2016-08-11 12:41:40 +00:00
{
SIM_MODEL::TYPE typeFromFields = SIM_MODEL::ReadTypeFromFields( aFields );
2016-08-11 12:41:40 +00:00
for( SIM_MODEL::TYPE type : SIM_MODEL::TYPE_ITERATOR() )
2016-08-11 12:41:40 +00:00
{
if( type == typeFromFields )
{
m_models.push_back( SIM_MODEL::Create( type, m_symbol.GetAllPins().size(),
&aFields ) );
m_curModelType = type;
}
else
m_models.push_back( SIM_MODEL::Create( type, m_symbol.GetAllPins().size() ) );
2016-08-11 12:41:40 +00:00
SIM_MODEL::DEVICE_TYPE deviceType = SIM_MODEL::TypeInfo( type ).deviceType;
2016-08-11 12:41:40 +00:00
// By default choose the first model type of each device type.
if( !m_curModelTypeOfDeviceType.count( deviceType ) )
m_curModelTypeOfDeviceType[deviceType] = type;
}
}
catch( KI_PARAM_ERROR& e )
{
DisplayErrorMessage( this, e.What() );
return;
}
2016-08-11 12:41:40 +00:00
m_typeChoice->Clear();
for( SIM_MODEL::DEVICE_TYPE deviceType : SIM_MODEL::DEVICE_TYPE_ITERATOR() )
m_deviceTypeChoice->Append( SIM_MODEL::DeviceTypeInfo( deviceType ).description );
m_scintillaTricks = std::make_unique<SCINTILLA_TRICKS>( m_codePreview, wxT( "{}" ), false );
2016-08-11 12:41:40 +00:00
m_paramGridMgr->Bind( wxEVT_PG_SELECTED, &DIALOG_SPICE_MODEL::onSelectionChange, this );
m_paramGrid->SetValidationFailureBehavior( wxPG_VFB_STAY_IN_PROPERTY
| wxPG_VFB_BEEP
| wxPG_VFB_MARK_CELL );
m_paramGrid->SetColumnProportion( static_cast<int>( PARAM_COLUMN::DESCRIPTION ), 50 );
m_paramGrid->SetColumnProportion( static_cast<int>( PARAM_COLUMN::VALUE ), 18 );
m_paramGrid->SetColumnProportion( static_cast<int>( PARAM_COLUMN::UNIT ), 10 );
m_paramGrid->SetColumnProportion( static_cast<int>( PARAM_COLUMN::DEFAULT ), 12 );
m_paramGrid->SetColumnProportion( static_cast<int>( PARAM_COLUMN::TYPE ), 10 );
if( wxPropertyGrid* grid = m_paramGrid->GetGrid() )
{
grid->AddActionTrigger( wxPG_ACTION_EDIT, WXK_RETURN );
grid->DedicateKey( WXK_RETURN );
grid->AddActionTrigger( wxPG_ACTION_NEXT_PROPERTY, WXK_RETURN );
grid->DedicateKey( WXK_UP );
grid->DedicateKey( WXK_DOWN );
// Doesn't work for some reason.
//grid->DedicateKey( WXK_TAB );
//grid->AddActionTrigger( wxPG_ACTION_NEXT_PROPERTY, WXK_TAB );
//grid->AddActionTrigger( wxPG_ACTION_PREV_PROPERTY, WXK_TAB, wxMOD_SHIFT );
}
else
wxFAIL;
Layout();
2016-08-11 12:41:40 +00:00
}
template <typename T>
bool DIALOG_SPICE_MODEL<T>::TransferDataFromWindow()
{
if( !DIALOG_SPICE_MODEL_BASE::TransferDataFromWindow() )
return false;
getCurModel().WriteFields( m_fields );
return true;
}
template <typename T>
bool DIALOG_SPICE_MODEL<T>::TransferDataToWindow()
2016-08-11 12:41:40 +00:00
{
try
2016-08-11 12:41:40 +00:00
{
m_models.at( static_cast<int>( SIM_MODEL::ReadTypeFromFields( m_fields ) ) )
= SIM_MODEL::Create( m_symbol.GetAllPins().size(), m_fields );
}
catch( KI_PARAM_ERROR& e )
2016-08-11 12:41:40 +00:00
{
DisplayErrorMessage( this, e.What() );
return DIALOG_SPICE_MODEL_BASE::TransferDataToWindow();
2016-08-11 12:41:40 +00:00
}
2016-08-11 12:41:56 +00:00
updateWidgets();
2016-08-11 12:41:56 +00:00
return DIALOG_SPICE_MODEL_BASE::TransferDataToWindow();
2016-08-11 12:41:40 +00:00
}
template <typename T>
void DIALOG_SPICE_MODEL<T>::updateWidgets()
{
updateModelParamsTab();
updateModelCodeTab();
updatePinAssignmentsTab();
}
template <typename T>
void DIALOG_SPICE_MODEL<T>::updateModelParamsTab()
2016-08-11 12:41:40 +00:00
{
SIM_MODEL::DEVICE_TYPE deviceType = SIM_MODEL::TypeInfo( m_curModelType ).deviceType;
m_deviceTypeChoice->SetSelection( static_cast<int>( deviceType ) );
m_typeChoice->Clear();
2016-08-11 12:41:40 +00:00
for( SIM_MODEL::TYPE type : SIM_MODEL::TYPE_ITERATOR() )
2016-08-11 12:41:40 +00:00
{
if( SIM_MODEL::TypeInfo( type ).deviceType == deviceType )
2016-08-11 12:41:40 +00:00
{
wxString description = SIM_MODEL::TypeInfo( type ).description;
if( !description.IsEmpty() )
m_typeChoice->Append( description );
if( type == m_curModelType )
m_typeChoice->SetSelection( m_typeChoice->GetCount() - 1 );
2016-08-11 12:41:40 +00:00
}
}
// This wxPropertyGridManager stuff has to be here because it segfaults in the constructor.
m_paramGridMgr->SetColumnCount( static_cast<int>( PARAM_COLUMN::END_ ) );
m_paramGridMgr->SetColumnTitle( static_cast<int>( PARAM_COLUMN::UNIT ), "Unit" );
m_paramGridMgr->SetColumnTitle( static_cast<int>( PARAM_COLUMN::DEFAULT ), "Default" );
m_paramGridMgr->SetColumnTitle( static_cast<int>( PARAM_COLUMN::TYPE ), "Type" );
m_paramGridMgr->ShowHeader();
m_paramGrid->Clear();
m_firstCategory = m_paramGrid->Append( new wxPropertyCategory( "DC" ) );
m_paramGrid->HideProperty( "DC" );
m_paramGrid->Append( new wxPropertyCategory( "Temperature" ) );
m_paramGrid->HideProperty( "Temperature" );
m_paramGrid->Append( new wxPropertyCategory( "Noise" ) );
m_paramGrid->HideProperty( "Noise" );
m_paramGrid->Append( new wxPropertyCategory( "Distributed Quantities" ) );
m_paramGrid->HideProperty( "Distributed Quantities" );
m_paramGrid->Append( new wxPropertyCategory( "Geometry" ) );
m_paramGrid->HideProperty( "Geometry" );
m_paramGrid->Append( new wxPropertyCategory( "Limiting Values" ) );
m_paramGrid->HideProperty( "Limiting Values" );
m_paramGrid->Append( new wxPropertyCategory( "Advanced" ) );
m_paramGrid->HideProperty( "Advanced" );
m_paramGrid->Append( new wxPropertyCategory( "Flags" ) );
m_paramGrid->HideProperty( "Flags" );
for( const SIM_MODEL::PARAM& param : getCurModel().Params() )
addParamPropertyIfRelevant( param );
m_paramGrid->CollapseAll();
2016-08-11 12:41:40 +00:00
}
template <typename T>
void DIALOG_SPICE_MODEL<T>::updateModelCodeTab()
2016-08-11 12:41:40 +00:00
{
}
2016-08-11 12:41:40 +00:00
template <typename T>
void DIALOG_SPICE_MODEL<T>::updatePinAssignmentsTab()
{
m_pinAssignmentsGrid->ClearRows();
std::vector<SCH_PIN*> pinList = m_symbol.GetAllPins();
m_pinAssignmentsGrid->AppendRows( static_cast<int>( pinList.size() ) );
for( int i = 0; i < m_pinAssignmentsGrid->GetNumberRows(); ++i )
{
wxString symbolPinString = getSymbolPinString( i + 1 );
m_pinAssignmentsGrid->SetReadOnly( i, static_cast<int>( PIN_COLUMN::SYMBOL ) );
m_pinAssignmentsGrid->SetCellValue( i, static_cast<int>( PIN_COLUMN::SYMBOL ),
symbolPinString );
// Using `new` here shouldn't cause a memory leak because `SetCellEditor()` calls
// `DecRef()` on its last editor.
m_pinAssignmentsGrid->SetCellEditor( i, static_cast<int>( PIN_COLUMN::MODEL ),
new wxGridCellChoiceEditor() );
m_pinAssignmentsGrid->SetCellValue( i, static_cast<int>( PIN_COLUMN::MODEL ),
"Not Connected" );
}
for( unsigned int i = 0; i < getCurModel().Pins().size(); ++i )
{
int symbolPinNumber = getCurModel().Pins().at( i ).symbolPinNumber;
if( symbolPinNumber == SIM_MODEL::PIN::NOT_CONNECTED )
continue;
wxString modelPinString = getModelPinString( i + 1 );
wxArrayString choices;
m_pinAssignmentsGrid->SetCellValue( symbolPinNumber - 1,
static_cast<int>( PIN_COLUMN::MODEL ),
modelPinString );
}
updatePinAssignmentsGridEditors();
// TODO: Show a preview of the symbol with the pin numbers shown.
// TODO: Maybe show a preview of the code for subcircuits and code models.
2016-08-11 12:41:40 +00:00
}
template <typename T>
void DIALOG_SPICE_MODEL<T>::updatePinAssignmentsGridEditors()
2016-08-11 12:41:40 +00:00
{
wxString modelPinChoicesString = "";
bool isFirst = true;
for( unsigned int i = 0; i < getCurModel().Pins().size(); ++i )
{
const SIM_MODEL::PIN& modelPin = getCurModel().Pins().at( i );
int modelPinNumber = static_cast<int>( i + 1 );
if( modelPin.symbolPinNumber != SIM_MODEL::PIN::NOT_CONNECTED )
continue;
if( isFirst )
{
modelPinChoicesString << getModelPinString( modelPinNumber );
isFirst = false;
}
else
modelPinChoicesString << "," << getModelPinString( modelPinNumber );
}
if( !isFirst )
modelPinChoicesString << ",";
modelPinChoicesString << "Not Connected";
for( int i = 0; i < m_pinAssignmentsGrid->GetNumberRows(); ++i )
{
wxGridCellChoiceEditor* editor = static_cast<wxGridCellChoiceEditor*>(
m_pinAssignmentsGrid->GetCellEditor( i, static_cast<int>( PIN_COLUMN::MODEL ) ) );
if( !editor )
{
// Shouldn't happen.
wxFAIL_MSG( "Grid cell editor is null" );
return;
}
wxString curModelPinString = m_pinAssignmentsGrid->GetCellValue(
i, static_cast<int>( PIN_COLUMN::MODEL ) );
if( curModelPinString == "Not Connected" )
editor->SetParameters( modelPinChoicesString );
else
editor->SetParameters( curModelPinString + "," + modelPinChoicesString );
}
}
template <typename T>
void DIALOG_SPICE_MODEL<T>::addParamPropertyIfRelevant( const SIM_MODEL::PARAM& aParam )
{
if( aParam.info.dir == SIM_MODEL::PARAM::DIR::OUT )
return;
switch( aParam.info.category )
{
case CATEGORY::DC:
m_paramGrid->HideProperty( "DC", false );
m_paramGrid->AppendIn( "DC", newParamProperty( aParam ) );
break;
case CATEGORY::CAPACITANCE:
m_paramGrid->HideProperty( "Capacitance", false );
m_paramGrid->AppendIn( "Capacitance", newParamProperty( aParam ) );
break;
case CATEGORY::TEMPERATURE:
m_paramGrid->HideProperty( "Temperature", false );
m_paramGrid->AppendIn( "Temperature", newParamProperty( aParam ) );
break;
case CATEGORY::NOISE:
m_paramGrid->HideProperty( "Noise", false );
m_paramGrid->AppendIn( "Noise", newParamProperty( aParam ) );
break;
case CATEGORY::DISTRIBUTED_QUANTITIES:
m_paramGrid->HideProperty( "Distributed Quantities", false );
m_paramGrid->AppendIn( "Distributed Quantities", newParamProperty( aParam ) );
break;
case CATEGORY::GEOMETRY:
m_paramGrid->HideProperty( "Geometry", false );
m_paramGrid->AppendIn( "Geometry", newParamProperty( aParam ) );
break;
case CATEGORY::LIMITING_VALUES:
m_paramGrid->HideProperty( "Limiting Values", false );
m_paramGrid->AppendIn( "Limiting Values", newParamProperty( aParam ) );
break;
case CATEGORY::ADVANCED:
m_paramGrid->HideProperty( "Advanced", false );
m_paramGrid->AppendIn( "Advanced", newParamProperty( aParam ) );
break;
case CATEGORY::FLAGS:
m_paramGrid->HideProperty( "Flags", false );
m_paramGrid->AppendIn( "Flags", newParamProperty( aParam ) );
break;
default:
//m_paramGrid->AppendIn( nullptr, newParamProperty( aParam ) );
m_paramGrid->Insert( m_firstCategory, newParamProperty( aParam ) );
//m_paramGrid->Append( newParamProperty( aParam ) );
break;
case CATEGORY::INITIAL_CONDITIONS:
case CATEGORY::SUPERFLUOUS:
return;
}
}
template <typename T>
wxPGProperty* DIALOG_SPICE_MODEL<T>::newParamProperty( const SIM_MODEL::PARAM& aParam ) const
{
wxString paramDescription = wxString::Format( "%s (%s)",
aParam.info.description,
aParam.info.name );
wxPGProperty* prop = nullptr;
switch( aParam.info.type )
{
case TYPE::INT:
prop = new SIM_PROPERTY( paramDescription,aParam.info.name, *aParam.value,
SIM_VALUE_BASE::TYPE::INT );
break;
case TYPE::FLOAT:
prop = new SIM_PROPERTY( paramDescription,aParam.info.name, *aParam.value,
SIM_VALUE_BASE::TYPE::FLOAT );
break;
case TYPE::BOOL:
prop = new wxBoolProperty( paramDescription, aParam.info.name );
prop->SetAttribute( wxPG_BOOL_USE_CHECKBOX, true );
break;
default:
prop = new wxStringProperty( paramDescription, aParam.info.name );
break;
}
prop->SetAttribute( wxPG_ATTR_UNITS, aParam.info.unit );
// Legacy due to the way we extracted the parameters from Ngspice.
if( aParam.isOtherVariant )
prop->SetCell( 3, aParam.info.defaultValueOfOtherVariant );
else
prop->SetCell( 3, aParam.info.defaultValue );
wxString typeStr;
switch( aParam.info.type )
{
case TYPE::BOOL: typeStr = wxString( "Bool" ); break;
case TYPE::INT: typeStr = wxString( "Int" ); break;
case TYPE::FLOAT: typeStr = wxString( "Float" ); break;
case TYPE::COMPLEX: typeStr = wxString( "Complex" ); break;
case TYPE::STRING: typeStr = wxString( "String" ); break;
case TYPE::BOOL_VECTOR: typeStr = wxString( "Bool Vector" ); break;
case TYPE::INT_VECTOR: typeStr = wxString( "Int Vector" ); break;
case TYPE::FLOAT_VECTOR: typeStr = wxString( "Float Vector" ); break;
case TYPE::COMPLEX_VECTOR: typeStr = wxString( "Complex Vector" ); break;
}
prop->SetCell( static_cast<int>( PARAM_COLUMN::TYPE ), typeStr );
return prop;
}
template <typename T>
SIM_MODEL& DIALOG_SPICE_MODEL<T>::getCurModel() const
{
return *m_models.at( static_cast<int>( m_curModelType ) );
}
template <typename T>
wxString DIALOG_SPICE_MODEL<T>::getSymbolPinString( int symbolPinNumber ) const
{
wxString name = "";
SCH_PIN* symbolPin = m_symbol.GetAllPins().at( symbolPinNumber - 1 );
if( symbolPin )
name = symbolPin->GetShownName();
LOCALE_IO toggle;
if( name.IsEmpty() )
return wxString::Format( "%d", symbolPinNumber );
else
return wxString::Format( "%d (%s)", symbolPinNumber, symbolPin->GetShownName() );
}
template <typename T>
wxString DIALOG_SPICE_MODEL<T>::getModelPinString( int modelPinNumber ) const
{
const wxString& pinName = getCurModel().Pins().at( modelPinNumber - 1 ).name;
LOCALE_IO toggle;
if( pinName.IsEmpty() )
return wxString::Format( "%d", modelPinNumber, pinName );
else
return wxString::Format( "%d (%s)", modelPinNumber, pinName );
}
template <typename T>
int DIALOG_SPICE_MODEL<T>::getModelPinNumber( const wxString& aModelPinString ) const
{
if( aModelPinString == "Not Connected" )
return SIM_MODEL::PIN::NOT_CONNECTED;
int length = aModelPinString.Find( " " );
if( length == wxNOT_FOUND )
length = static_cast<int>( aModelPinString.Length() );
long result = 0;
aModelPinString.Mid( 0, length ).ToCLong( &result );
return static_cast<int>( result );
}
template <typename T>
void DIALOG_SPICE_MODEL<T>::onDeviceTypeChoice( wxCommandEvent& aEvent )
{
SIM_MODEL::DEVICE_TYPE deviceType =
static_cast<SIM_MODEL::DEVICE_TYPE>( m_deviceTypeChoice->GetSelection() );
m_curModelType = m_curModelTypeOfDeviceType.at( deviceType );
updateWidgets();
}
template <typename T>
void DIALOG_SPICE_MODEL<T>::onTypeChoice( wxCommandEvent& aEvent )
{
SIM_MODEL::DEVICE_TYPE deviceType =
static_cast<SIM_MODEL::DEVICE_TYPE>( m_deviceTypeChoice->GetSelection() );
wxString typeDescription = m_typeChoice->GetString( m_typeChoice->GetSelection() );
for( SIM_MODEL::TYPE type : SIM_MODEL::TYPE_ITERATOR() )
{
if( deviceType == SIM_MODEL::TypeInfo( type ).deviceType
&& typeDescription == SIM_MODEL::TypeInfo( type ).description )
{
m_curModelType = type;
break;
}
}
m_curModelTypeOfDeviceType.at( deviceType ) = m_curModelType;
updateWidgets();
}
template <typename T>
void DIALOG_SPICE_MODEL<T>::onPinAssignmentsGridCellChange( wxGridEvent& aEvent )
{
int symbolPinNumber = aEvent.GetRow() + 1;
int oldModelPinNumber = getModelPinNumber( aEvent.GetString() );
int modelPinNumber = getModelPinNumber(
m_pinAssignmentsGrid->GetCellValue( aEvent.GetRow(), aEvent.GetCol() ) );
if( oldModelPinNumber != SIM_MODEL::PIN::NOT_CONNECTED )
getCurModel().Pins().at( oldModelPinNumber - 1 ).symbolPinNumber =
SIM_MODEL::PIN::NOT_CONNECTED;
if( modelPinNumber != SIM_MODEL::PIN::NOT_CONNECTED )
getCurModel().Pins().at( modelPinNumber - 1 ).symbolPinNumber = symbolPinNumber;
updatePinAssignmentsGridEditors();
aEvent.Skip();
}
template <typename T>
void DIALOG_SPICE_MODEL<T>::onPinAssignmentsGridSize( wxSizeEvent& aEvent )
{
wxGridUpdateLocker deferRepaintsTillLeavingScope( m_pinAssignmentsGrid );
int gridWidth = KIPLATFORM::UI::GetUnobscuredSize( m_pinAssignmentsGrid ).x;
m_pinAssignmentsGrid->SetColSize( static_cast<int>( PIN_COLUMN::MODEL ), gridWidth / 2 );
m_pinAssignmentsGrid->SetColSize( static_cast<int>( PIN_COLUMN::SYMBOL ), gridWidth / 2 );
aEvent.Skip();
}
template <typename T>
void DIALOG_SPICE_MODEL<T>::onSelectionChange( wxPropertyGridEvent& aEvent )
{
// TODO: Activate also when the whole property grid is selected with tab key.
wxPropertyGrid* grid = m_paramGrid->GetGrid();
if( !grid )
{
wxFAIL;
return;
}
wxWindow* editorControl = grid->GetEditorControl();
if( !editorControl )
{
wxFAIL;
return;
}
// Without this, the user had to press tab before they could edit the field.
editorControl->SetFocus();
}
/*template <typename T>
void DIALOG_SPICE_MODEL<T>::onPropertyChanged( wxPropertyGridEvent& aEvent )
{
wxString name = aEvent.GetPropertyName();
for( SIM_MODEL::PARAM& param : getCurModel().Params() )
{
if( param.info.name == name )
{
try
{
param.value->FromString( m_paramGrid->GetPropertyValueAsString( param.info.name ) );
}
catch( KI_PARAM_ERROR& e )
{
DisplayErrorMessage( this, e.What() );
}
}
}
}*/