/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2020 CERN * * @author Wayne Stambaugh * * 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, see . */ #include #include #include // WildCompareString #include #include #include #include #include #include #include #include #include #include bool g_removeExtraFields = false; bool g_resetEmptyFields = false; bool g_resetFieldVisibilities = false; bool g_resetFieldEffects = false; bool g_resetFieldPositions = false; DIALOG_CHANGE_SYMBOLS::DIALOG_CHANGE_SYMBOLS( SCH_EDIT_FRAME* aParent, SCH_COMPONENT* aSymbol, MODE aMode ) : DIALOG_CHANGE_SYMBOLS_BASE( aParent ), m_symbol( aSymbol), m_mode( aMode ) { wxASSERT( aParent ); wxString label; wxString verb = ( m_mode == MODE::UPDATE ) ? _( "Update" ) : _( "Change" ); wxString reset = ( m_mode == MODE::UPDATE ) ? _( "Reset" ) : _( "Update" ); wxString library = ( m_mode == MODE::UPDATE ) ? _( "library" ) : _( "new" ); label.Printf( m_matchAll->GetLabel(), verb ); if( m_mode == MODE::UPDATE ) { m_matchAll->SetLabel( label ); SetTitle( _( "Update Symbol(s) from Library" ) ); m_newIdSizer->Show( false ); } else { SetTitle( _( "Change Symbol(s)" ) ); m_matchSizer->FindItem( m_matchAll )->Show( false ); } if( m_symbol ) { SCH_SHEET_PATH* currentSheet = &aParent->Schematic().CurrentSheet(); label.Printf( m_matchBySelection->GetLabel(), verb ); m_matchBySelection->SetLabel( label ); m_newId->AppendText( FROM_UTF8( m_symbol->GetLibId().Format().c_str() ) ); m_specifiedReference->ChangeValue( m_symbol->GetRef( currentSheet ) ); m_specifiedValue->ChangeValue( m_symbol->GetValue( currentSheet ) ); m_specifiedId->ChangeValue( FROM_UTF8( m_symbol->GetLibId().Format().c_str() ) ); } else { m_matchSizer->FindItem( m_matchBySelection )->Show( false ); } m_matchIdBrowserButton->SetBitmap( KiBitmap( small_library_xpm ) ); m_newIdBrowserButton->SetBitmap( KiBitmap( small_library_xpm ) ); label.Printf( m_matchByReference->GetLabel(), verb ); m_matchByReference->SetLabel( label ); label.Printf( m_matchByValue->GetLabel(), verb ); m_matchByValue->SetLabel( label ); label.Printf( m_matchById->GetLabel(), verb ); m_matchById->SetLabel( label ); m_matchSizer->SetEmptyCellSize( wxSize( 0, 0 ) ); m_matchSizer->Layout(); for( int i = 0; i < MANDATORY_FIELDS; ++i ) { m_fieldsBox->Append( TEMPLATE_FIELDNAME::GetDefaultFieldName( i ) ); if( i != REFERENCE && i != VALUE ) m_fieldsBox->Check( i, true ); } updateFieldsList(); m_messagePanel->SetLazyUpdate( true ); if( aSymbol && aSymbol->IsSelected() ) { m_matchBySelection->SetValue( true ); } else { if( aMode == MODE::UPDATE ) m_matchAll->SetValue( true ); else m_matchByReference->SetValue( true ); } label.Printf( m_updateFieldsSizer->GetStaticBox()->GetLabel(), reset ); m_updateFieldsSizer->GetStaticBox()->SetLabel( label ); label.Printf( m_removeExtraBox->GetLabel(), library ); m_removeExtraBox->SetLabel( label ); label.Printf( m_resetEmptyFields->GetLabel(), library ); m_resetEmptyFields->SetLabel( label ); label.Printf( m_resetFieldVisibilities->GetLabel(), reset ); m_resetFieldVisibilities->SetLabel( label ); label.Printf( m_resetFieldEffects->GetLabel(), reset ); m_resetFieldEffects->SetLabel( label ); label.Printf( m_resetFieldPositions->GetLabel(), reset ); m_resetFieldPositions->SetLabel( label ); m_removeExtraBox->SetValue( g_removeExtraFields ); m_resetEmptyFields->SetValue( g_resetEmptyFields ); m_resetFieldVisibilities->SetValue( g_resetFieldVisibilities ); m_resetFieldEffects->SetValue( g_resetFieldEffects ); m_resetFieldPositions->SetValue( g_resetFieldPositions ); // DIALOG_SHIM needs a unique hash_key because classname is not sufficient // because the update and change versions of this dialog have different controls. m_hash_key = TO_UTF8( GetTitle() ); // Ensure m_closeButton (with id = wxID_CANCEL) has the right label // (to fix automatic renaming of button label ) m_sdbSizerCancel->SetLabel( _( "Close" ) ); m_sdbSizerOK->SetLabel( verb ); m_sdbSizerOK->SetDefault(); // Now all widgets have the size fixed, call FinishDialogSettings FinishDialogSettings(); } void DIALOG_CHANGE_SYMBOLS::onMatchByAll( wxCommandEvent& aEvent ) { updateFieldsList(); } void DIALOG_CHANGE_SYMBOLS::onMatchBySelected( wxCommandEvent& aEvent ) { updateFieldsList(); } void DIALOG_CHANGE_SYMBOLS::onMatchByReference( wxCommandEvent& aEvent ) { updateFieldsList(); m_specifiedReference->SetFocus(); } void DIALOG_CHANGE_SYMBOLS::onMatchByValue( wxCommandEvent& aEvent ) { updateFieldsList(); m_specifiedValue->SetFocus(); } void DIALOG_CHANGE_SYMBOLS::onMatchById( wxCommandEvent& aEvent ) { updateFieldsList(); m_specifiedId->SetFocus(); } void DIALOG_CHANGE_SYMBOLS::onMatchTextKillFocus( wxFocusEvent& event ) { updateFieldsList(); } DIALOG_CHANGE_SYMBOLS::~DIALOG_CHANGE_SYMBOLS() { g_removeExtraFields = m_removeExtraBox->GetValue(); g_resetEmptyFields = m_resetEmptyFields->GetValue(); g_resetFieldVisibilities = m_resetFieldVisibilities->GetValue(); g_resetFieldEffects = m_resetFieldEffects->GetValue(); g_resetFieldPositions = m_resetFieldPositions->GetValue(); } void DIALOG_CHANGE_SYMBOLS::launchMatchIdSymbolBrowser( wxCommandEvent& aEvent ) { wxString newName = m_specifiedId->GetValue(); KIWAY_PLAYER* frame = Kiway().Player( FRAME_SCH_VIEWER_MODAL, true ); if( frame->ShowModal( &newName, this ) ) { m_specifiedId->SetValue( newName ); updateFieldsList(); } frame->Destroy(); } void DIALOG_CHANGE_SYMBOLS::launchNewIdSymbolBrowser( wxCommandEvent& aEvent ) { wxString newName = m_newId->GetValue(); KIWAY_PLAYER* frame = Kiway().Player( FRAME_SCH_VIEWER_MODAL, true ); if( frame->ShowModal( &newName, this ) ) { m_newId->SetValue( newName ); updateFieldsList(); } frame->Destroy(); } void DIALOG_CHANGE_SYMBOLS::updateFieldsList() { SCH_EDIT_FRAME* frame = dynamic_cast( GetParent() ); LIB_ID newId; SCH_SHEET_LIST hierarchy = frame->Schematic().GetSheets(); if( m_mode == MODE::CHANGE ) { newId.Parse( m_newId->GetValue(), LIB_ID::ID_SCH ); if( !newId.IsValid() ) return; } // Load new non-mandatory fields from the library parts of all matching symbols std::set fieldNames; for( SCH_SHEET_PATH& instance : hierarchy ) { SCH_SCREEN* screen = instance.LastScreen(); wxCHECK2( screen, continue ); for( SCH_ITEM* item : screen->Items().OfType( SCH_COMPONENT_T ) ) { SCH_COMPONENT* symbol = dynamic_cast( item ); wxCHECK2( symbol, continue ); if( !isMatch( symbol, &instance ) ) continue; LIB_PART* libSymbol = frame->GetLibPart( symbol->GetLibId() ); if( !libSymbol ) continue; std::unique_ptr flattenedSymbol = libSymbol->Flatten(); LIB_FIELDS libFields; flattenedSymbol->GetFields( libFields ); for( unsigned i = MANDATORY_FIELDS; i < libFields.size(); ++i ) fieldNames.insert( libFields[i].GetName() ); } } // Update the listbox widget for( unsigned i = m_fieldsBox->GetCount() - 1; i >= MANDATORY_FIELDS; --i ) m_fieldsBox->Delete( i ); for( const wxString& fieldName : fieldNames ) m_fieldsBox->Append( fieldName ); for( unsigned i = MANDATORY_FIELDS; i < m_fieldsBox->GetCount(); ++i ) m_fieldsBox->Check( i, true ); } void DIALOG_CHANGE_SYMBOLS::checkAll( bool aCheck ) { for( unsigned i = 0; i < m_fieldsBox->GetCount(); ++i ) m_fieldsBox->Check( i, aCheck ); } void DIALOG_CHANGE_SYMBOLS::onOkButtonClicked( wxCommandEvent& aEvent ) { wxBusyCursor dummy; SCH_EDIT_FRAME* parent = dynamic_cast( GetParent() ); wxCHECK( parent, /* void */ ); m_messagePanel->Clear(); m_messagePanel->Flush( false ); // Create the set of fields to be updated m_updateFields.clear(); for( unsigned i = 0; i < m_fieldsBox->GetCount(); ++i ) { if( m_fieldsBox->IsChecked( i ) ) m_updateFields.insert( m_fieldsBox->GetString( i ) ); } if( processMatchingSymbols() ) { parent->TestDanglingEnds(); // This will also redraw the changed symbols. parent->OnModify(); } m_messagePanel->Flush( false ); } bool DIALOG_CHANGE_SYMBOLS::isMatch( SCH_COMPONENT* aSymbol, SCH_SHEET_PATH* aInstance ) { LIB_ID id; wxCHECK( aSymbol, false ); SCH_EDIT_FRAME* frame = dynamic_cast( GetParent() ); wxCHECK( frame, false ); if( m_matchAll->GetValue() ) { return true; } else if( m_matchBySelection->GetValue() ) { return aSymbol == m_symbol; } else if( m_matchByReference->GetValue() ) { return WildCompareString( m_specifiedReference->GetValue(), aSymbol->GetRef( aInstance ), false ); } else if( m_matchByValue->GetValue() ) { return WildCompareString( m_specifiedValue->GetValue(), aSymbol->GetValue( aInstance ), false ); } else if( m_matchById ) { id.Parse( m_specifiedId->GetValue(), LIB_ID::ID_SCH ); return aSymbol->GetLibId() == id; } return false; } bool DIALOG_CHANGE_SYMBOLS::processMatchingSymbols() { SCH_EDIT_FRAME* frame = dynamic_cast( GetParent() ); wxCHECK( frame, false ); LIB_ID newId; bool appendToUndo = false; bool changed = false; SCH_SHEET_LIST hierarchy = frame->Schematic().GetSheets(); if( m_mode == MODE::CHANGE ) { newId.Parse( m_newId->GetValue(), LIB_ID::ID_SCH ); if( !newId.IsValid() ) return false; } // Use map as a cheap and dirty way to ensure library symbols are not updated multiple // times in complex hierachies. std::map symbolsToProcess; for( SCH_SHEET_PATH& instance : hierarchy ) { SCH_SCREEN* screen = instance.LastScreen(); wxCHECK2( screen, continue ); for( SCH_ITEM* item : screen->Items().OfType( SCH_COMPONENT_T ) ) { SCH_COMPONENT* symbol = dynamic_cast( item ); wxCHECK2( symbol, continue ); if( !isMatch( symbol, &instance ) ) continue; // Shared symbols always have identical library symbols so don't add duplicates. symbolsToProcess[symbol] = screen; } } for( auto i : symbolsToProcess ) { SCH_COMPONENT* symbol = i.first; if( m_mode == MODE::UPDATE ) { if( processSymbol( symbol, i.second, symbol->GetLibId(), appendToUndo ) ) changed = true; } else { if( processSymbol( symbol, i.second, newId, appendToUndo ) ) changed = true; } if( changed ) appendToUndo = true; } return changed; } bool DIALOG_CHANGE_SYMBOLS::processSymbol( SCH_COMPONENT* aSymbol, SCH_SCREEN* aScreen, const LIB_ID& aNewId, bool aAppendToUndo ) { wxCHECK( aSymbol, false ); wxCHECK( aScreen, false ); wxCHECK( aNewId.IsValid(), false ); SCH_EDIT_FRAME* frame = dynamic_cast( GetParent() ); wxCHECK( frame, false ); LIB_ID oldId = aSymbol->GetLibId(); wxString msg; wxString references; for( COMPONENT_INSTANCE_REFERENCE instance : aSymbol->GetInstanceReferences() ) { if( references.IsEmpty() ) references = instance.m_Reference; else references += " " + instance.m_Reference; } msg.Printf( _( "%s %s \"%s\" from \"%s\" to \"%s\"" ), ( m_mode == MODE::UPDATE ) ? _( "Update" ) : _( "Change" ), ( aSymbol->GetInstanceReferences().size() == 1 ) ? _( "symbol" ) : _( "symbols" ), references, oldId.Format().c_str(), aNewId.Format().c_str() ); LIB_PART* libSymbol = frame->GetLibPart( aNewId ); if( !libSymbol ) { msg << ": " << _( "*** symbol not found ***" ); m_messagePanel->Report( msg, RPT_SEVERITY_ERROR ); return false; } std::unique_ptr< LIB_PART > flattenedSymbol = libSymbol->Flatten(); if( flattenedSymbol->GetUnitCount() < aSymbol->GetUnit() ) { msg << ": " << _( "*** new symbol has too few units ***" ); m_messagePanel->Report( msg, RPT_SEVERITY_ERROR ); return false; } // Removing the symbol needs to be done before the LIB_PART is changed to prevent stale // library symbols in the schematic file. aScreen->Remove( aSymbol ); frame->SaveCopyInUndoList( aScreen, aSymbol, UNDO_REDO::CHANGED, aAppendToUndo ); if( aNewId != aSymbol->GetLibId() ) aSymbol->SetLibId( aNewId ); aSymbol->SetLibSymbol( flattenedSymbol.release() ); bool removeExtras = m_removeExtraBox->GetValue(); bool resetEmpty = m_resetEmptyFields->GetValue(); bool resetVis = m_resetFieldVisibilities->GetValue(); bool resetEffects = m_resetFieldEffects->GetValue(); bool resetPositions = m_resetFieldPositions->GetValue(); for( unsigned i = 0; i < aSymbol->GetFields().size(); ++i ) { SCH_FIELD* field = aSymbol->GetField( (int) i ) ; LIB_FIELD* libField = nullptr; if( alg::contains( m_updateFields, field->GetName() ) ) continue; if( i < MANDATORY_FIELDS ) libField = libSymbol->GetField( (int) i ); else libField = libSymbol->FindField( field->GetName() ); if( libField ) { if( resetEmpty && libField->GetText().IsEmpty() ) field->SetText( wxEmptyString ); if( resetVis ) field->SetVisible( libField->IsVisible() ); if( resetEffects ) { // Careful: the visible bit is also in Effects bool visible = field->IsVisible(); field->SetEffects( *libField ); field->SetVisible( visible ); } if( resetPositions ) { field->SetTextPos( aSymbol->GetPosition() + libField->GetTextPos() ); } } else if( i >= MANDATORY_FIELDS && removeExtras ) { aSymbol->RemoveField( field->GetName() ); i--; } } LIB_FIELDS libFields; libSymbol->GetFields( libFields ); for( unsigned i = MANDATORY_FIELDS; i < libFields.size(); ++i ) { LIB_FIELD& libField = libFields[i]; if( !alg::contains( m_updateFields, libField.GetName() ) ) continue; if( !aSymbol->FindField( libField.GetName(), false ) ) { wxString fieldName = libField.GetCanonicalName(); SCH_FIELD newField( wxPoint( 0, 0), aSymbol->GetFieldCount(), aSymbol, fieldName ); SCH_FIELD* schField = aSymbol->AddField( newField ); schField->SetEffects( libField ); schField->SetText( libField.GetText() ); schField->SetTextPos( aSymbol->GetPosition() + libField.GetTextPos() ); } } aScreen->Append( aSymbol ); frame->GetCanvas()->GetView()->Update( aSymbol ); msg += ": OK"; m_messagePanel->Report( msg, RPT_SEVERITY_ACTION ); return true; }