/* * 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. * * 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 using TYPE = SIM_VALUE_BASE::TYPE; using CATEGORY = SIM_MODEL::PARAM::CATEGORY; template class DIALOG_SPICE_MODEL; template class DIALOG_SPICE_MODEL; template DIALOG_SPICE_MODEL::DIALOG_SPICE_MODEL( wxWindow* aParent, SCH_SYMBOL& aSymbol, std::vector& aFields ) : DIALOG_SPICE_MODEL_BASE( aParent ), m_symbol( aSymbol ), m_fields( aFields ), m_firstCategory( nullptr ) { try { SIM_MODEL::TYPE typeFromFields = SIM_MODEL::ReadTypeFromFields( aFields ); for( SIM_MODEL::TYPE type : SIM_MODEL::TYPE_ITERATOR() ) { 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() ) ); SIM_MODEL::DEVICE_TYPE deviceType = SIM_MODEL::TypeInfo( type ).deviceType; // 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; } 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( m_codePreview, wxT( "{}" ), false ); 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( PARAM_COLUMN::DESCRIPTION ), 50 ); m_paramGrid->SetColumnProportion( static_cast( PARAM_COLUMN::VALUE ), 18 ); m_paramGrid->SetColumnProportion( static_cast( PARAM_COLUMN::UNIT ), 10 ); m_paramGrid->SetColumnProportion( static_cast( PARAM_COLUMN::DEFAULT ), 12 ); m_paramGrid->SetColumnProportion( static_cast( 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(); } template bool DIALOG_SPICE_MODEL::TransferDataFromWindow() { if( !DIALOG_SPICE_MODEL_BASE::TransferDataFromWindow() ) return false; getCurModel().WriteFields( m_fields ); return true; } template bool DIALOG_SPICE_MODEL::TransferDataToWindow() { try { m_models.at( static_cast( SIM_MODEL::ReadTypeFromFields( m_fields ) ) ) = SIM_MODEL::Create( m_symbol.GetAllPins().size(), m_fields ); } catch( KI_PARAM_ERROR& e ) { DisplayErrorMessage( this, e.What() ); return DIALOG_SPICE_MODEL_BASE::TransferDataToWindow(); } updateWidgets(); return DIALOG_SPICE_MODEL_BASE::TransferDataToWindow(); } template void DIALOG_SPICE_MODEL::updateWidgets() { updateModelParamsTab(); updateModelCodeTab(); updatePinAssignmentsTab(); } template void DIALOG_SPICE_MODEL::updateModelParamsTab() { SIM_MODEL::DEVICE_TYPE deviceType = SIM_MODEL::TypeInfo( m_curModelType ).deviceType; m_deviceTypeChoice->SetSelection( static_cast( deviceType ) ); m_typeChoice->Clear(); for( SIM_MODEL::TYPE type : SIM_MODEL::TYPE_ITERATOR() ) { if( SIM_MODEL::TypeInfo( type ).deviceType == deviceType ) { 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 ); } } // This wxPropertyGridManager stuff has to be here because it segfaults in the constructor. m_paramGridMgr->SetColumnCount( static_cast( PARAM_COLUMN::END_ ) ); m_paramGridMgr->SetColumnTitle( static_cast( PARAM_COLUMN::UNIT ), "Unit" ); m_paramGridMgr->SetColumnTitle( static_cast( PARAM_COLUMN::DEFAULT ), "Default" ); m_paramGridMgr->SetColumnTitle( static_cast( 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(); } template void DIALOG_SPICE_MODEL::updateModelCodeTab() { } template void DIALOG_SPICE_MODEL::updatePinAssignmentsTab() { m_pinAssignmentsGrid->ClearRows(); std::vector pinList = m_symbol.GetAllPins(); m_pinAssignmentsGrid->AppendRows( static_cast( pinList.size() ) ); for( int i = 0; i < m_pinAssignmentsGrid->GetNumberRows(); ++i ) { wxString symbolPinString = getSymbolPinString( i + 1 ); m_pinAssignmentsGrid->SetReadOnly( i, static_cast( PIN_COLUMN::SYMBOL ) ); m_pinAssignmentsGrid->SetCellValue( i, static_cast( 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( PIN_COLUMN::MODEL ), new wxGridCellChoiceEditor() ); m_pinAssignmentsGrid->SetCellValue( i, static_cast( 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( 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. } template void DIALOG_SPICE_MODEL::updatePinAssignmentsGridEditors() { 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( 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( m_pinAssignmentsGrid->GetCellEditor( i, static_cast( PIN_COLUMN::MODEL ) ) ); if( !editor ) { // Shouldn't happen. wxFAIL_MSG( "Grid cell editor is null" ); return; } wxString curModelPinString = m_pinAssignmentsGrid->GetCellValue( i, static_cast( PIN_COLUMN::MODEL ) ); if( curModelPinString == "Not Connected" ) editor->SetParameters( modelPinChoicesString ); else editor->SetParameters( curModelPinString + "," + modelPinChoicesString ); } } template void DIALOG_SPICE_MODEL::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 wxPGProperty* DIALOG_SPICE_MODEL::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( PARAM_COLUMN::TYPE ), typeStr ); return prop; } template SIM_MODEL& DIALOG_SPICE_MODEL::getCurModel() const { return *m_models.at( static_cast( m_curModelType ) ); } template wxString DIALOG_SPICE_MODEL::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 wxString DIALOG_SPICE_MODEL::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 int DIALOG_SPICE_MODEL::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( aModelPinString.Length() ); long result = 0; aModelPinString.Mid( 0, length ).ToCLong( &result ); return static_cast( result ); } template void DIALOG_SPICE_MODEL::onDeviceTypeChoice( wxCommandEvent& aEvent ) { SIM_MODEL::DEVICE_TYPE deviceType = static_cast( m_deviceTypeChoice->GetSelection() ); m_curModelType = m_curModelTypeOfDeviceType.at( deviceType ); updateWidgets(); } template void DIALOG_SPICE_MODEL::onTypeChoice( wxCommandEvent& aEvent ) { SIM_MODEL::DEVICE_TYPE deviceType = static_cast( 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 void DIALOG_SPICE_MODEL::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 void DIALOG_SPICE_MODEL::onPinAssignmentsGridSize( wxSizeEvent& aEvent ) { wxGridUpdateLocker deferRepaintsTillLeavingScope( m_pinAssignmentsGrid ); int gridWidth = KIPLATFORM::UI::GetUnobscuredSize( m_pinAssignmentsGrid ).x; m_pinAssignmentsGrid->SetColSize( static_cast( PIN_COLUMN::MODEL ), gridWidth / 2 ); m_pinAssignmentsGrid->SetColSize( static_cast( PIN_COLUMN::SYMBOL ), gridWidth / 2 ); aEvent.Skip(); } template void DIALOG_SPICE_MODEL::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 void DIALOG_SPICE_MODEL::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() ); } } } }*/