/* * 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 #include #include #include #include #include #include #include #include #include #include using CATEGORY = SIM_MODEL::PARAM::CATEGORY; template DIALOG_SIM_MODEL::DIALOG_SIM_MODEL( wxWindow* aParent, SCH_SYMBOL& aSymbol, std::vector& aFields ) : DIALOG_SIM_MODEL_BASE( aParent ), m_symbol( aSymbol ), m_fields( aFields ), m_libraryModelsMgr( Prj() ), m_builtinModelsMgr( Prj() ), m_prevModel( nullptr ), m_curModelType( SIM_MODEL::TYPE::NONE ), m_scintillaTricks( nullptr ), m_wasCodePreviewUpdated( true ), m_firstCategory( nullptr ), m_prevParamGridSelection( nullptr ) { m_modelNameCombobox->SetValidator( m_modelNameValidator ); m_browseButton->SetBitmap( KiBitmap( BITMAPS::small_folder ) ); // Note that to get ALL pins, not only of the current part, you need to use // m_symbol.GetRawPins(). m_sortedPartPins = m_symbol.GetLibPins(); std::sort( m_sortedPartPins.begin(), m_sortedPartPins.end(), []( const LIB_PIN* lhs, const LIB_PIN* rhs ) { // We sort by StrNumCmp because SIM_MODEL_BASE sorts with it too. return StrNumCmp( lhs->GetNumber(), rhs->GetNumber(), true ) < 0; } ); m_typeChoice->Clear(); m_scintillaTricks = new SCINTILLA_TRICKS( m_codePreview, wxT( "{}" ), false ); m_paramGridMgr->Bind( wxEVT_PG_SELECTED, &DIALOG_SIM_MODEL::onParamGridSelectionChange, this ); m_paramGrid->SetValidationFailureBehavior( wxPG_VFB_STAY_IN_PROPERTY | wxPG_VFB_BEEP | wxPG_VFB_MARK_CELL ); m_paramGrid->SetColumnProportion( PARAM_COLUMN::DESCRIPTION, 50 ); m_paramGrid->SetColumnProportion( PARAM_COLUMN::VALUE, 18 ); m_paramGrid->SetColumnProportion( PARAM_COLUMN::UNIT, 10 ); m_paramGrid->SetColumnProportion( PARAM_COLUMN::DEFAULT, 12 ); m_paramGrid->SetColumnProportion( PARAM_COLUMN::TYPE, 10 ); if( wxPropertyGrid* grid = m_paramGrid->GetGrid() ) { //grid->SetCellBackgroundColour( grid->GetPropertyDefaultCell().GetBgCol() ); //grid->SetCellTextColour( grid->GetPropertyDefaultCell().GetFgCol(); // In wx 3.0 the color will be wrong sometimes. grid->SetCellDisabledTextColour( wxSystemSettings::GetColour( wxSYS_COLOUR_GRAYTEXT ) ); grid->Bind( wxEVT_SET_FOCUS, &DIALOG_SIM_MODEL::onParamGridSetFocus, this ); 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 ); } else { wxFAIL; } m_pinAssignmentsGrid->PushEventHandler( new GRID_TRICKS( m_pinAssignmentsGrid ) ); // Now all widgets have the size fixed, call FinishDialogSettings finishDialogSettings(); } template DIALOG_SIM_MODEL::~DIALOG_SIM_MODEL() { // Disable all properties. This is necessary because some of their methods are called after // destruction of DIALOG_SIM_MODEL, oddly. When disabled, they never access their models. for( wxPropertyGridIterator it = m_paramGrid->GetIterator(); !it.AtEnd(); ++it ) { SIM_PROPERTY* prop = dynamic_cast( *it ); if( !prop ) continue; prop->Disable(); } // Delete the GRID_TRICKS. m_pinAssignmentsGrid->PopEventHandler( true ); delete m_scintillaTricks; } template bool DIALOG_SIM_MODEL::TransferDataToWindow() { wxCommandEvent dummyEvent; std::string libraryFilename = SIM_MODEL::GetFieldValue( &m_fields, SIM_LIBRARY::LIBRARY_FIELD ); if( libraryFilename != "" ) { // The model is sourced from a library, optionally with instance overrides. m_useLibraryModelRadioButton->SetValue( true ); loadLibrary( libraryFilename ); // Must be set before curModel() is used since the latter checks the combobox value. m_modelNameCombobox->SetStringSelection( SIM_MODEL::GetFieldValue( &m_fields, SIM_LIBRARY::NAME_FIELD ) ); if( isIbisLoaded() && ( m_modelNameCombobox->GetSelection() >= 0 ) ) { SIM_MODEL_KIBIS* kibismodel = dynamic_cast( &m_libraryModelsMgr.GetModels().at( m_modelNameCombobox->GetSelection() ).get() ); if( kibismodel ) { onModelNameCombobox( dummyEvent ); // refresh list of pins int i = 0; for( std::pair strs : kibismodel->GetIbisPins() ) { if( strs.first == SIM_MODEL::GetFieldValue( &m_fields, SIM_LIBRARY_KIBIS::PIN_FIELD ) ) { auto kibisLibrary = static_cast( library() ); kibismodel->ChangePin( *kibisLibrary, strs.first ); m_ibisPinCombobox->SetSelection( static_cast( i ) ); break; } i++; } if( i < static_cast( kibismodel->GetIbisPins().size() ) ) { onIbisPinCombobox( dummyEvent ); // refresh list of models m_ibisModelCombobox->SetStringSelection( SIM_MODEL::GetFieldValue( &m_fields, SIM_LIBRARY_KIBIS::MODEL_FIELD ) ); } if( SIM_MODEL::GetFieldValue( &m_fields, SIM_LIBRARY_KIBIS::DIFF_FIELD ) == "1" ) { kibismodel->SwitchSingleEndedDiff( true ); m_differentialCheckbox->SetValue( true ); } else { kibismodel->SwitchSingleEndedDiff( false ); m_differentialCheckbox->SetValue( false ); } } } } else { // The model is sourced from the instance. m_useInstanceModelRadioButton->SetValue( true ); m_curModelType = SIM_MODEL::ReadTypeFromFields( m_fields, m_symbol.GetRawPins().size() ); } for( SIM_MODEL::TYPE type : SIM_MODEL::TYPE_ITERATOR() ) { try { if( m_useInstanceModelRadioButton->GetValue() && type == m_curModelType ) m_builtinModelsMgr.CreateModel( m_fields, m_symbol.GetRawPins().size() ); else m_builtinModelsMgr.CreateModel( type, m_symbol.GetRawPins().size() ); } catch( const IO_ERROR& e ) { DisplayErrorMessage( this, _( "Failed to read simulation model from fields." ) + wxT( "\n\n" ) + e.What() ); } SIM_MODEL::DEVICE_T deviceType = SIM_MODEL::TypeInfo( type ).deviceType; if( !m_curModelTypeOfDeviceType.count( deviceType ) ) m_curModelTypeOfDeviceType[deviceType] = type; } m_saveInValueCheckbox->SetValue( curModel().IsStoredInValue() ); m_excludeCheckbox->SetValue( !curModel().IsEnabled() ); onRadioButton( dummyEvent ); return DIALOG_SIM_MODEL_BASE::TransferDataToWindow(); } template bool DIALOG_SIM_MODEL::TransferDataFromWindow() { m_pinAssignmentsGrid->CommitPendingChanges(); // This should have been done in wxPGTextCtrlEditor::OnTextCtrlEvent(), but something must // be clearing it before we get here, resulting in CommitChangesFromEditor() doing nothing m_paramGrid->GetGrid()->EditorsValueWasModified(); m_paramGrid->GetGrid()->CommitChangesFromEditor(); if( !DIALOG_SIM_MODEL_BASE::TransferDataFromWindow() ) return false; std::string modelName; modelName = m_modelNameCombobox->GetValue(); SIM_MODEL::SetFieldValue( m_fields, SIM_LIBRARY::NAME_FIELD, modelName ); std::string path; if( ( library() && m_useLibraryModelRadioButton->GetValue() ) || isIbisLoaded() ) { path = library()->GetFilePath(); wxFileName fn( path ); if( fn.MakeRelativeTo( Prj().GetProjectPath() ) && !fn.GetFullPath().StartsWith( ".." ) ) path = fn.GetFullPath(); } SIM_MODEL::SetFieldValue( m_fields, SIM_LIBRARY::LIBRARY_FIELD, path ); if( isIbisLoaded() ) { SIM_MODEL_KIBIS* kibismodel = dynamic_cast( &m_libraryModelsMgr.GetModels().at( m_modelNameCombobox->GetSelection() ).get() ); if( kibismodel ) { SIM_MODEL::SetFieldValue( m_fields, SIM_LIBRARY_KIBIS::PIN_FIELD, kibismodel->GetIbisPins().at( m_ibisPinCombobox->GetSelection() ).first ); SIM_MODEL::SetFieldValue( m_fields, SIM_LIBRARY_KIBIS::MODEL_FIELD, std::string( m_ibisModelCombobox->GetValue().c_str() ) ); SIM_MODEL::SetFieldValue( m_fields, SIM_LIBRARY_KIBIS::DIFF_FIELD, ( kibismodel->CanDifferential() && m_differentialCheckbox->GetValue() ) ? "1" : "" ); } } curModel().WriteFields( m_fields ); return true; } template void DIALOG_SIM_MODEL::updateWidgets() { updateIbisWidgets(); updateInstanceWidgets(); updateModelParamsTab(); updateModelCodeTab(); updatePinAssignments(); m_excludeCheckbox->SetValue( !curModel().IsEnabled() ); std::string ref = SIM_MODEL::GetFieldValue( &m_fields, SIM_MODEL::REFERENCE_FIELD ); m_modelPanel->Layout(); m_pinAssignmentsPanel->Layout(); m_parametersPanel->Layout(); m_codePanel->Layout(); SendSizeEvent( wxSEND_EVENT_POST ); m_prevModel = &curModel(); } template void DIALOG_SIM_MODEL::updateIbisWidgets() { SIM_MODEL_KIBIS* modelkibis = isIbisLoaded() ? dynamic_cast( &curModel() ) : nullptr; m_ibisModelCombobox->Show( isIbisLoaded() ); m_ibisPinCombobox->Show( isIbisLoaded() ); m_ibisModelLabel->Show( isIbisLoaded() ); m_ibisPinLabel->Show( isIbisLoaded() ); m_differentialCheckbox->Show( isIbisLoaded() && modelkibis && modelkibis->CanDifferential() ); m_modelNameLabel->SetLabel( isIbisLoaded() ? "Component:" : "Model:" ); } template void DIALOG_SIM_MODEL::updateInstanceWidgets() { // Change the Type choice to match the current device type. if( !m_prevModel || m_prevModel != &curModel() ) { m_deviceTypeChoice->Clear(); if( m_useLibraryModelRadioButton->GetValue() ) { m_deviceTypeChoice->Append( curModel().GetDeviceInfo().description ); m_deviceTypeChoice->SetSelection( 0 ); } else { for( SIM_MODEL::DEVICE_T deviceType : SIM_MODEL::DEVICE_T_ITERATOR() ) { if( !SIM_MODEL::DeviceInfo( deviceType ).isBuiltin ) continue; m_deviceTypeChoice->Append( SIM_MODEL::DeviceInfo( deviceType ).description ); if( deviceType == curModel().GetDeviceType() ) m_deviceTypeChoice->SetSelection( m_deviceTypeChoice->GetCount() - 1 ); } } m_typeChoice->Clear(); for( SIM_MODEL::TYPE type : SIM_MODEL::TYPE_ITERATOR() ) { if( SIM_MODEL::TypeInfo( type ).deviceType == curModel().GetDeviceType() ) { m_typeChoice->Append( SIM_MODEL::TypeInfo( type ).description ); if( type == curModel().GetType() ) m_typeChoice->SetSelection( m_typeChoice->GetCount() - 1 ); } } } m_typeChoice->Enable( !m_useLibraryModelRadioButton->GetValue() || isIbisLoaded() ); if( dynamic_cast( &curModel() ) ) m_modelNotebook->SetSelection( 1 ); else m_modelNotebook->SetSelection( 0 ); if( curModel().HasPrimaryValue() ) { const SIM_MODEL::PARAM& primary = curModel().GetParam( 0 ); m_saveInValueCheckbox->SetLabel( wxString::Format( _( "Save parameter '%s (%s)' in Value " "field" ), primary.info.description, primary.info.name ) ); m_saveInValueCheckbox->Enable( true ); } else { m_saveInValueCheckbox->SetLabel( _( "Save primary parameter in Value field" ) ); m_saveInValueCheckbox->SetValue( false ); m_saveInValueCheckbox->Enable( false ); } } template void DIALOG_SIM_MODEL::updateModelParamsTab() { if( &curModel() != m_prevModel ) { // This wxPropertyGridManager column and header stuff has to be here because it segfaults in // the constructor. m_paramGridMgr->SetColumnCount( PARAM_COLUMN::END_ ); m_paramGridMgr->SetColumnTitle( PARAM_COLUMN::DESCRIPTION, _( "Parameter" ) ); m_paramGridMgr->SetColumnTitle( PARAM_COLUMN::UNIT, _( "Unit" ) ); m_paramGridMgr->SetColumnTitle( PARAM_COLUMN::DEFAULT, _( "Default" ) ); m_paramGridMgr->SetColumnTitle( PARAM_COLUMN::TYPE, _( "Type" ) ); m_paramGridMgr->ShowHeader(); m_paramGrid->Clear(); m_firstCategory = m_paramGrid->Append( new wxPropertyCategory( "Geometry" ) ); m_paramGrid->HideProperty( "Geometry" ); m_paramGrid->Append( new wxPropertyCategory( "AC" ) ); m_paramGrid->HideProperty( "AC" ); m_paramGrid->Append( new wxPropertyCategory( "DC" ) ); m_paramGrid->HideProperty( "DC" ); m_paramGrid->Append( new wxPropertyCategory( "Capacitance" ) ); m_paramGrid->HideProperty( "Capacitance" ); 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( "Waveform" ) ); m_paramGrid->HideProperty( "Waveform" ); 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" ); m_paramGrid->CollapseAll(); for( int i = 0; i < curModel().GetParamCount(); ++i ) addParamPropertyIfRelevant( i ); m_paramGrid->CollapseAll(); m_paramGrid->Expand( "AC" ); m_paramGrid->Expand( "Waveform" ); } // Set all properties to default colors. // Update properties in models that have autofill. for( wxPropertyGridIterator it = m_paramGrid->GetIterator(); !it.AtEnd(); ++it ) { wxColour bgCol = m_paramGrid->GetGrid()->GetPropertyDefaultCell().GetBgCol(); wxColour fgCol = m_paramGrid->GetGrid()->GetPropertyDefaultCell().GetFgCol(); for( int col = 0; col < m_paramGridMgr->GetColumnCount(); ++col ) { ( *it )->GetCell( col ).SetBgCol( bgCol ); ( *it )->GetCell( col ).SetFgCol( fgCol ); } SIM_PROPERTY* prop = dynamic_cast( *it ); if( !prop ) continue; const SIM_MODEL::PARAM& param = prop->GetParam(); // Model values other than the currently edited value may have changed. Update them. // This feature is called "autofill" and present only in certain models. Don't do it for // models that don't have it for performance reasons. if( curModel().HasAutofill() ) ( *it )->SetValueFromString( param.value->ToString() ); } } template void DIALOG_SIM_MODEL::updateModelCodeTab() { wxString text; SPICE_ITEM item; item.modelName = m_modelNameCombobox->GetStringSelection(); if( m_useInstanceModelRadioButton->GetValue() || item.modelName == "" ) item.modelName = m_fields.at( REFERENCE_FIELD ).GetText(); SIM_MODEL& model = curModel(); text << model.SpiceGenerator().Preview( item ); if( SIM_MODEL_RAW_SPICE* rawSpice = dynamic_cast( &model ) ) text << rawSpice->GetSource(); m_codePreview->SetText( text ); m_wasCodePreviewUpdated = true; } template void DIALOG_SIM_MODEL::updatePinAssignments() { removeOrphanedPinAssignments(); // Reset the grid. m_pinAssignmentsGrid->ClearRows(); m_pinAssignmentsGrid->AppendRows( static_cast( m_sortedPartPins.size() ) ); for( int row = 0; row < m_pinAssignmentsGrid->GetNumberRows(); ++row ) m_pinAssignmentsGrid->SetCellValue( row, PIN_COLUMN::MODEL, _( "Not Connected" ) ); // Now set up the grid values in the Model column. for( int modelPinIndex = 0; modelPinIndex < curModel().GetPinCount(); ++modelPinIndex ) { wxString symbolPinNumber = curModel().GetPin( modelPinIndex ).symbolPinNumber; if( symbolPinNumber == "" ) continue; int symbolPinRow = findSymbolPinRow( symbolPinNumber ); if( symbolPinRow == -1 ) continue; wxString modelPinString = getModelPinString( modelPinIndex ); m_pinAssignmentsGrid->SetCellValue( symbolPinRow, PIN_COLUMN::MODEL, modelPinString ); } // Set up the Symbol column grid values and Model column cell editors with dropdown options. for( int ii = 0; ii < m_pinAssignmentsGrid->GetNumberRows(); ++ii ) { wxString symbolPinString = getSymbolPinString( ii ); m_pinAssignmentsGrid->SetReadOnly( ii, PIN_COLUMN::SYMBOL ); m_pinAssignmentsGrid->SetCellValue( ii, PIN_COLUMN::SYMBOL, symbolPinString ); wxString curModelPinString = m_pinAssignmentsGrid->GetCellValue( ii, PIN_COLUMN::MODEL ); std::vector modelPinIcons; wxArrayString modelPinChoices; for( int jj = 0; jj < curModel().GetPinCount(); ++jj ) { if( curModel().GetPin( jj ).symbolPinNumber != "" ) modelPinIcons.push_back( PinShapeGetBitmap( GRAPHIC_PINSHAPE::LINE ) ); else modelPinIcons.push_back( BITMAPS::INVALID_BITMAP ); modelPinChoices.Add( getModelPinString( jj ) ); } modelPinIcons.push_back( BITMAPS::INVALID_BITMAP ); modelPinChoices.Add( _( "Not Connected" ) ); // Using `new` here shouldn't cause a memory leak because `SetCellEditor()` calls // `DecRef()` on its last editor. m_pinAssignmentsGrid->SetCellEditor( ii, PIN_COLUMN::MODEL, new GRID_CELL_ICON_TEXT_POPUP( modelPinIcons, modelPinChoices ) ); } // 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_SIM_MODEL::removeOrphanedPinAssignments() { for( int i = 0; i < curModel().GetPinCount(); ++i ) { const SIM_MODEL::PIN& modelPin = curModel().GetPin( i ); const std::vector>& allPins = m_symbol.GetRawPins(); if( std::none_of( allPins.begin(), allPins.end(), [modelPin]( const std::unique_ptr& symbolPin ) { return modelPin.symbolPinNumber == symbolPin->GetNumber(); } ) ) { // Is orphaned. curModel().SetPinSymbolPinNumber( i, "" ); } } } template void DIALOG_SIM_MODEL::loadLibrary( const wxString& aLibraryPath, bool aForceReload ) { auto libraries = m_libraryModelsMgr.GetLibraries(); // Loading the same library as previously should normally be a no-op except when done using the // library browse button. if( !aForceReload && libraries.size() >= 1 && libraries.begin()->first == aLibraryPath ) return; DIALOG_IBIS_PARSER_REPORTER dlg( this ); dlg.m_messagePanel->Clear(); bool tryingToLoadIbis = false; if( aLibraryPath.EndsWith( ".ibs" ) ) tryingToLoadIbis = true; try { m_libraryModelsMgr.SetLibrary( std::string( aLibraryPath.ToUTF8() ), &dlg.m_messagePanel->Reporter() ); } catch( const IO_ERROR& e ) { if( tryingToLoadIbis ) { dlg.m_messagePanel->Flush(); dlg.ShowQuasiModal(); } else { DisplayErrorMessage( this, e.What() ); } return; } try { std::string modelName = SIM_MODEL::GetFieldValue( &m_fields, SIM_LIBRARY::NAME_FIELD ); int pinCount = m_symbol.GetRawPins().size(); for( auto& [baseModelName, baseModel] : library()->GetModels() ) { if( baseModelName == modelName ) m_libraryModelsMgr.CreateModel( baseModel, pinCount, m_fields ); else m_libraryModelsMgr.CreateModel( baseModel, pinCount ); } } catch( const IO_ERROR& e ) { DisplayErrorMessage( this, e.What() ); } wxArrayString modelNames; for( auto& [modelName, model] : library()->GetModels() ) modelNames.Add( modelName ); auto validator = dynamic_cast( m_modelNameCombobox->GetValidator() ); if( validator ) validator->SetIncludes( modelNames ); m_libraryPathText->ChangeValue( aLibraryPath ); m_modelNameCombobox->Set( modelNames ); m_useLibraryModelRadioButton->SetValue( true ); if( isIbisLoaded() ) { wxArrayString emptyArray; m_ibisModelCombobox->Set( emptyArray ); m_ibisPinCombobox->Set( emptyArray ); m_ibisModelCombobox->SetSelection( -1 ); m_ibisPinCombobox->SetSelection( -1 ); } } template void DIALOG_SIM_MODEL::addParamPropertyIfRelevant( int aParamIndex ) { if( curModel().GetParam( aParamIndex ).info.dir == SIM_MODEL::PARAM::DIR_OUT ) return; switch( curModel().GetParam( aParamIndex ).info.category ) { case CATEGORY::AC: m_paramGrid->HideProperty( "AC", false ); m_paramGrid->AppendIn( "AC", newParamProperty( aParamIndex ) ); break; case CATEGORY::DC: m_paramGrid->HideProperty( "DC", false ); m_paramGrid->AppendIn( "DC", newParamProperty( aParamIndex ) ); break; case CATEGORY::CAPACITANCE: m_paramGrid->HideProperty( "Capacitance", false ); m_paramGrid->AppendIn( "Capacitance", newParamProperty( aParamIndex ) ); break; case CATEGORY::TEMPERATURE: m_paramGrid->HideProperty( "Temperature", false ); m_paramGrid->AppendIn( "Temperature", newParamProperty( aParamIndex ) ); break; case CATEGORY::NOISE: m_paramGrid->HideProperty( "Noise", false ); m_paramGrid->AppendIn( "Noise", newParamProperty( aParamIndex ) ); break; case CATEGORY::DISTRIBUTED_QUANTITIES: m_paramGrid->HideProperty( "Distributed Quantities", false ); m_paramGrid->AppendIn( "Distributed Quantities", newParamProperty( aParamIndex ) ); break; case CATEGORY::WAVEFORM: m_paramGrid->HideProperty( "Waveform", false ); m_paramGrid->AppendIn( "Waveform", newParamProperty( aParamIndex ) ); break; case CATEGORY::GEOMETRY: m_paramGrid->HideProperty( "Geometry", false ); m_paramGrid->AppendIn( "Geometry", newParamProperty( aParamIndex ) ); break; case CATEGORY::LIMITING_VALUES: m_paramGrid->HideProperty( "Limiting Values", false ); m_paramGrid->AppendIn( "Limiting Values", newParamProperty( aParamIndex ) ); break; case CATEGORY::ADVANCED: m_paramGrid->HideProperty( "Advanced", false ); m_paramGrid->AppendIn( "Advanced", newParamProperty( aParamIndex ) ); break; case CATEGORY::FLAGS: m_paramGrid->HideProperty( "Flags", false ); m_paramGrid->AppendIn( "Flags", newParamProperty( aParamIndex ) ); break; default: m_paramGrid->Insert( m_firstCategory, newParamProperty( aParamIndex ) ); break; case CATEGORY::INITIAL_CONDITIONS: case CATEGORY::SUPERFLUOUS: return; } } template wxPGProperty* DIALOG_SIM_MODEL::newParamProperty( int aParamIndex ) const { const SIM_MODEL::PARAM& param = curModel().GetParam( aParamIndex ); wxString paramDescription; if( param.info.description == "" ) paramDescription = wxString::Format( "%s", param.info.name ); else paramDescription = wxString::Format( "%s (%s)", param.info.description, param.info.name ); wxPGProperty* prop = nullptr; switch( param.info.type ) { case SIM_VALUE::TYPE_BOOL: // TODO. prop = new SIM_BOOL_PROPERTY( paramDescription, param.info.name, curModel(), aParamIndex ); prop->SetAttribute( wxPG_BOOL_USE_CHECKBOX, true ); break; case SIM_VALUE::TYPE_INT: prop = new SIM_STRING_PROPERTY( paramDescription, param.info.name, curModel(), aParamIndex, SIM_VALUE::TYPE_INT ); break; case SIM_VALUE::TYPE_FLOAT: prop = new SIM_STRING_PROPERTY( paramDescription, param.info.name, curModel(), aParamIndex, SIM_VALUE::TYPE_FLOAT ); break; //case TYPE_COMPLEX: // break; case SIM_VALUE::TYPE_STRING: if( param.info.enumValues.empty() ) { prop = new SIM_STRING_PROPERTY( paramDescription, param.info.name, curModel(), aParamIndex, SIM_VALUE::TYPE_STRING ); } else { prop = new SIM_ENUM_PROPERTY( paramDescription, param.info.name, curModel(), aParamIndex ); } break; default: prop = new wxStringProperty( paramDescription, param.info.name ); break; } prop->SetAttribute( wxPG_ATTR_UNITS, wxString::FromUTF8( param.info.unit.c_str() ) ); // Legacy due to the way we extracted the parameters from Ngspice. #if wxCHECK_VERSION( 3, 1, 0 ) if( param.isOtherVariant ) prop->SetCell( 3, wxString::FromUTF8( param.info.defaultValueOfOtherVariant ) ); else prop->SetCell( 3, wxString::FromUTF8( param.info.defaultValue ) ); #else if( param.isOtherVariant ) prop->SetCell( 3, wxString::FromUTF8( param.info.defaultValueOfOtherVariant.c_str() ) ); else prop->SetCell( 3, wxString::FromUTF8( param.info.defaultValue.c_str() ) ); #endif wxString typeStr; switch( param.info.type ) { case SIM_VALUE::TYPE_BOOL: typeStr = wxT( "Bool" ); break; case SIM_VALUE::TYPE_INT: typeStr = wxT( "Int" ); break; case SIM_VALUE::TYPE_FLOAT: typeStr = wxT( "Float" ); break; case SIM_VALUE::TYPE_COMPLEX: typeStr = wxT( "Complex" ); break; case SIM_VALUE::TYPE_STRING: typeStr = wxT( "String" ); break; case SIM_VALUE::TYPE_BOOL_VECTOR: typeStr = wxT( "Bool Vector" ); break; case SIM_VALUE::TYPE_INT_VECTOR: typeStr = wxT( "Int Vector" ); break; case SIM_VALUE::TYPE_FLOAT_VECTOR: typeStr = wxT( "Float Vector" ); break; case SIM_VALUE::TYPE_COMPLEX_VECTOR: typeStr = wxT( "Complex Vector" ); break; } prop->SetCell( PARAM_COLUMN::TYPE, typeStr ); return prop; } template int DIALOG_SIM_MODEL::findSymbolPinRow( const wxString& aSymbolPinNumber ) const { for( int row = 0; row < static_cast( m_sortedPartPins.size() ); ++row ) { LIB_PIN* pin = m_sortedPartPins[row]; if( pin->GetNumber() == aSymbolPinNumber ) return row; } return -1; } template SIM_MODEL& DIALOG_SIM_MODEL::curModel() const { if( m_useLibraryModelRadioButton->GetValue() && m_modelNameCombobox->GetSelection() != wxNOT_FOUND ) { return m_libraryModelsMgr.GetModels().at( m_modelNameCombobox->GetSelection() ).get(); } else { return m_builtinModelsMgr.GetModels().at( static_cast( m_curModelType ) ); } } template const SIM_LIBRARY* DIALOG_SIM_MODEL::library() const { if( m_libraryModelsMgr.GetLibraries().size() == 1 ) return &m_libraryModelsMgr.GetLibraries().begin()->second.get(); return nullptr; } template wxString DIALOG_SIM_MODEL::getSymbolPinString( int symbolPinIndex ) const { LIB_PIN* pin = m_sortedPartPins.at( symbolPinIndex ); wxString pinNumber; wxString pinName; if( pin ) { pinNumber = pin->GetShownNumber(); pinName = pin->GetShownName(); } if( !pinName.IsEmpty() && pinName != pinNumber ) pinNumber += wxString::Format( wxT( " (%s)" ), pinName ); return pinNumber; } template wxString DIALOG_SIM_MODEL::getModelPinString( int aModelPinIndex ) const { const wxString& pinName = curModel().GetPin( aModelPinIndex ).name; LOCALE_IO toggle; wxString pinNumber = wxString::Format( "%d", aModelPinIndex + 1 ); if( !pinName.IsEmpty() && pinName != pinNumber ) pinNumber += wxString::Format( wxT( " (%s)" ), pinName ); return pinNumber; } template int DIALOG_SIM_MODEL::getModelPinIndex( 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 - 1 ); } template void DIALOG_SIM_MODEL::onRadioButton( wxCommandEvent& aEvent ) { bool fromLibrary = m_useLibraryModelRadioButton->GetValue(); m_pathLabel->Enable( fromLibrary ); m_libraryPathText->Enable( fromLibrary ); m_browseButton->Enable( fromLibrary ); m_modelNameLabel->Enable( fromLibrary ); m_modelNameCombobox->Enable( fromLibrary ); m_ibisPinLabel->Enable( fromLibrary ); m_ibisPinCombobox->Enable( fromLibrary ); m_differentialCheckbox->Enable( fromLibrary ); m_ibisModelLabel->Enable( fromLibrary ); m_ibisModelCombobox->Enable( fromLibrary ); m_staticTextDevType->Enable( !fromLibrary ); m_deviceTypeChoice->Enable( !fromLibrary ); m_staticTextSpiceType->Enable( !fromLibrary ); updateWidgets(); } template void DIALOG_SIM_MODEL::onLibraryPathTextEnter( wxCommandEvent& aEvent ) { if( m_useLibraryModelRadioButton->GetValue() ) { wxString path = m_libraryPathText->GetValue(); wxFileName fn( path ); if( fn.MakeRelativeTo( Prj().GetProjectPath() ) && !fn.GetFullPath().StartsWith( ".." ) ) path = fn.GetFullPath(); try { loadLibrary( path ); updateWidgets(); } catch( const IO_ERROR& ) { // TODO: Add an infobar to report the error? } } } template void DIALOG_SIM_MODEL::onLibraryPathTextKillFocus( wxFocusEvent& aEvent ) { wxCommandEvent dummy; onLibraryPathTextEnter( dummy ); } template void DIALOG_SIM_MODEL::onBrowseButtonClick( wxCommandEvent& aEvent ) { wxFileDialog dlg( this, _( "Browse Models" ), Prj().GetProjectPath() ); if( dlg.ShowModal() == wxID_CANCEL ) return; wxString path = dlg.GetPath(); wxFileName fn( path ); if( fn.MakeRelativeTo( Prj().GetProjectPath() ) && !fn.GetFullPath().StartsWith( ".." ) ) path = fn.GetFullPath(); loadLibrary( path, true ); updateWidgets(); } template void DIALOG_SIM_MODEL::onModelNameCombobox( wxCommandEvent& aEvent ) { if( isIbisLoaded() ) { wxArrayString pinLabels; SIM_MODEL_KIBIS* modelkibis = dynamic_cast( &curModel() ); wxCHECK2( modelkibis, return ); for( std::pair strs : modelkibis->GetIbisPins() ) pinLabels.Add( strs.first + wxT( " - " ) + strs.second ); m_ibisPinCombobox->Set( pinLabels ); wxArrayString emptyArray; m_ibisModelCombobox->Set( emptyArray ); } updateWidgets(); } template void DIALOG_SIM_MODEL::onModelNameComboboxKillFocus( wxFocusEvent& aEvent ) { m_modelNameCombobox->SetSelection( m_modelNameCombobox->FindString( m_modelNameCombobox->GetValue() ) ); updateWidgets(); } template void DIALOG_SIM_MODEL::onModelNameComboboxTextEnter( wxCommandEvent& aEvent ) { m_modelNameCombobox->SetSelection( m_modelNameCombobox->FindString( m_modelNameCombobox->GetValue() ) ); onModelNameCombobox( aEvent ); } template void DIALOG_SIM_MODEL::onIbisPinCombobox( wxCommandEvent& aEvent ) { if( isIbisLoaded() ) { wxArrayString modelLabels; SIM_MODEL_KIBIS& kibisModel = static_cast( curModel() ); std::vector> strs = kibisModel.GetIbisPins(); std::string pinNumber = strs.at( m_ibisPinCombobox->GetSelection() ).first; const SIM_LIBRARY_KIBIS* kibisLibrary = dynamic_cast( library() ); kibisModel.ChangePin( *kibisLibrary, pinNumber ); kibisModel.m_enableDiff = static_cast( library() ) ->isPinDiff( kibisModel.GetComponentName(), pinNumber ); for( wxString modelName : kibisModel.GetIbisModels() ) modelLabels.Add( modelName ); m_ibisModelCombobox->Set( modelLabels ); } updateWidgets(); } template void DIALOG_SIM_MODEL::onIbisPinComboboxTextEnter( wxCommandEvent& aEvent ) { m_ibisPinCombobox->SetSelection( m_ibisPinCombobox->FindString( m_ibisPinCombobox->GetValue() ) ); onIbisPinCombobox( aEvent ); } template void DIALOG_SIM_MODEL::onIbisModelCombobox( wxCommandEvent& aEvent ) { updateWidgets(); } template void DIALOG_SIM_MODEL::onIbisModelComboboxTextEnter( wxCommandEvent& aEvent ) { m_ibisModelCombobox->SetSelection( m_ibisModelCombobox->FindString( m_ibisModelCombobox->GetValue() ) ); onIbisPinCombobox( aEvent ); } template void DIALOG_SIM_MODEL::onDifferentialCheckbox( wxCommandEvent& aEvent ) { if( isIbisLoaded() ) { SIM_MODEL_KIBIS* modelkibis = dynamic_cast( &curModel() ); bool diff = m_differentialCheckbox->GetValue() && modelkibis->CanDifferential(); modelkibis->SwitchSingleEndedDiff( diff ); } updateWidgets(); } template void DIALOG_SIM_MODEL::onDeviceTypeChoice( wxCommandEvent& aEvent ) { for( SIM_MODEL::DEVICE_T deviceType : SIM_MODEL::DEVICE_T_ITERATOR() ) { if( SIM_MODEL::DeviceInfo( deviceType ).description == m_deviceTypeChoice->GetStringSelection() ) { m_curModelType = m_curModelTypeOfDeviceType.at( deviceType ); break; } } updateWidgets(); } template void DIALOG_SIM_MODEL::onTypeChoice( wxCommandEvent& aEvent ) { SIM_MODEL::DEVICE_T deviceType = curModel().GetDeviceType(); 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 ) { if( isIbisLoaded() && ( type == SIM_MODEL::TYPE::KIBIS_DEVICE || type == SIM_MODEL::TYPE::KIBIS_DRIVER_DC || type == SIM_MODEL::TYPE::KIBIS_DRIVER_RECT || type == SIM_MODEL::TYPE::KIBIS_DRIVER_PRBS ) ) { SIM_MODEL_KIBIS& kibisModel = static_cast( m_libraryModelsMgr.GetModels().at( m_modelNameCombobox->GetSelection() ).get() ); m_libraryModelsMgr.SetModel( m_modelNameCombobox->GetSelection(), std::make_unique( type, kibisModel, m_fields ) ); } m_curModelType = type; break; } } m_curModelTypeOfDeviceType.at( deviceType ) = m_curModelType; updateWidgets(); } template void DIALOG_SIM_MODEL::onParamGridChanged( wxPropertyGridEvent& aEvent ) { updateWidgets(); } template void DIALOG_SIM_MODEL::onCodePreviewSetFocus( wxFocusEvent& aEvent ) { // For some reason all text gets selected for me -Mikolaj if( m_wasCodePreviewUpdated ) m_codePreview->SelectNone(); m_wasCodePreviewUpdated = false; } template void DIALOG_SIM_MODEL::onPinAssignmentsGridCellChange( wxGridEvent& aEvent ) { int symbolPinIndex = aEvent.GetRow(); wxString oldModelPinName = aEvent.GetString(); wxString modelPinName = m_pinAssignmentsGrid->GetCellValue( aEvent.GetRow(), aEvent.GetCol() ); int oldModelPinIndex = getModelPinIndex( oldModelPinName ); int modelPinIndex = getModelPinIndex( modelPinName ); if( oldModelPinIndex != SIM_MODEL::PIN::NOT_CONNECTED ) curModel().SetPinSymbolPinNumber( oldModelPinIndex, "" ); if( modelPinIndex != SIM_MODEL::PIN::NOT_CONNECTED ) { curModel().SetPinSymbolPinNumber( modelPinIndex, std::string( m_sortedPartPins.at( symbolPinIndex )->GetShownNumber().ToUTF8() ) ); } updatePinAssignments(); aEvent.Skip(); } template void DIALOG_SIM_MODEL::onPinAssignmentsGridSize( wxSizeEvent& aEvent ) { wxGridUpdateLocker deferRepaintsTillLeavingScope( m_pinAssignmentsGrid ); int gridWidth = KIPLATFORM::UI::GetUnobscuredSize( m_pinAssignmentsGrid ).x; m_pinAssignmentsGrid->SetColSize( PIN_COLUMN::MODEL, gridWidth / 2 ); m_pinAssignmentsGrid->SetColSize( PIN_COLUMN::SYMBOL, gridWidth / 2 ); aEvent.Skip(); } template void DIALOG_SIM_MODEL::onSaveInValueCheckbox( wxCommandEvent& aEvent ) { curModel().SetIsStoredInValue( m_saveInValueCheckbox->GetValue() ); } template void DIALOG_SIM_MODEL::onExcludeCheckbox( wxCommandEvent& aEvent ) { curModel().SetIsEnabled( !m_excludeCheckbox->GetValue() ); } template void DIALOG_SIM_MODEL::onParamGridSetFocus( wxFocusEvent& aEvent ) { // By default, when a property grid is focused, the textbox is not immediately focused until // Tab key is pressed. This is inconvenient, so we fix that here. wxPropertyGrid* grid = m_paramGrid->GetGrid(); grid->CommitChangesFromEditor(); wxPGProperty* selected = grid->GetSelection(); if( !selected ) selected = grid->wxPropertyGridInterface::GetFirst(); if( selected ) grid->DoSelectProperty( selected, wxPG_SEL_FOCUS ); aEvent.Skip(); } template void DIALOG_SIM_MODEL::onParamGridSelectionChange( wxPropertyGridEvent& aEvent ) { wxPropertyGrid* grid = m_paramGrid->GetGrid(); grid->CommitChangesFromEditor(); // Jump over categories. if( grid->GetSelection() && grid->GetSelection()->IsCategory() ) { wxPGProperty* selection = grid->GetSelection(); // If the new selection is immediately above the previous selection, we jump up. Otherwise // we jump down. We do this by simulating up or down arrow keys. wxPropertyGridIterator it = grid->GetIterator( wxPG_ITERATE_VISIBLE, selection ); it.Next(); wxKeyEvent* keyEvent = new wxKeyEvent( wxEVT_KEY_DOWN ); if( *it == m_prevParamGridSelection ) { if( !selection->IsExpanded() ) { grid->Expand( selection ); keyEvent->m_keyCode = WXK_DOWN; wxQueueEvent( grid, keyEvent ); // Does not work for some reason. /*m_paramGrid->DoSelectProperty( selection->Item( selection->GetChildCount() - 1 ), wxPG_SEL_FOCUS );*/ } else { keyEvent->m_keyCode = WXK_UP; wxQueueEvent( grid, keyEvent ); } } else { if( !selection->IsExpanded() ) grid->Expand( selection ); keyEvent->m_keyCode = WXK_DOWN; wxQueueEvent( grid, keyEvent ); } m_prevParamGridSelection = grid->GetSelection(); return; } wxWindow* editorControl = grid->GetEditorControl(); if( !editorControl ) { m_prevParamGridSelection = grid->GetSelection(); return; } // Without this the user had to press tab before they could edit the field. editorControl->SetFocus(); m_prevParamGridSelection = grid->GetSelection(); } template class DIALOG_SIM_MODEL; template class DIALOG_SIM_MODEL;