kicad-source/eeschema/dialogs/dialog_change_symbols.cpp
Jeff Young 7b05e456cc Bug fixes for multiple symbol instances in complex hierarchies
1) use SCH_COMPONENT::GetRef(), GetValue() and GetFootprint() when
instance-specific info is needed
2) update UpdateAllScreenReferences() to handle value and footprint.
3) BACKANNO is CvPcb's handler, not back annotation's handler.  Which
means it needs GUI behaviour, not back annotation behaviour.

Fixes https://gitlab.com/kicad/code/kicad/issues/5520
2020-09-06 13:57:14 +01:00

444 lines
13 KiB
C++

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2020 CERN
*
* @author Wayne Stambaugh <stambaughw@gmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <algorithm>
#include <fctsys.h>
#include <bitmaps.h>
#include <kicad_string.h> // WildCompareString
#include <kiway.h>
#include <lib_id.h>
#include <dialog_change_symbols.h>
#include <sch_component.h>
#include <sch_edit_frame.h>
#include <sch_screen.h>
#include <sch_sheet_path.h>
#include <schematic.h>
#include <template_fieldnames.h>
#include <wx_html_report_panel.h>
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" );
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();
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 );
}
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::onMatchByReference( wxCommandEvent& aEvent )
{
m_specifiedReference->SetFocus();
}
void DIALOG_CHANGE_SYMBOLS::onMatchByValue( wxCommandEvent& aEvent )
{
m_specifiedValue->SetFocus();
}
void DIALOG_CHANGE_SYMBOLS::onMatchById( wxCommandEvent& aEvent )
{
m_specifiedId->SetFocus();
}
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 );
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 );
frame->Destroy();
}
void DIALOG_CHANGE_SYMBOLS::onOkButtonClicked( wxCommandEvent& aEvent )
{
wxBusyCursor dummy;
SCH_EDIT_FRAME* parent = dynamic_cast<SCH_EDIT_FRAME*>( GetParent() );
wxCHECK( parent, /* void */ );
m_messagePanel->Clear();
m_messagePanel->Flush( false );
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<SCH_EDIT_FRAME*>( 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<SCH_EDIT_FRAME*>( 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<SCH_COMPONENT*, SCH_SCREEN*> 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<SCH_COMPONENT*>( 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<SCH_EDIT_FRAME*>( 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( 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( !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;
}