kicad/eeschema/dialogs/dialog_change_symbols.cpp

582 lines
17 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" );
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<SCH_EDIT_FRAME*>( 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<wxString> 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<SCH_COMPONENT*>( item );
wxCHECK2( symbol, continue );
if( !isMatch( symbol, &instance ) )
continue;
LIB_PART* libSymbol = frame->GetLibPart( symbol->GetLibId() );
if( !libSymbol )
continue;
std::unique_ptr<LIB_PART> 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<SCH_EDIT_FRAME*>( 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<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( 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;
}