Files
kicad-source-mirror/eeschema/dialogs/dialog_import_symbol_select.cpp
2026-01-22 11:24:53 -08:00

576 lines
16 KiB
C++

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright The 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, see <http://www.gnu.org/licenses/>.
*/
#include "dialog_import_symbol_select.h"
#include <bitmaps.h>
#include <confirm.h>
#include <kidialog.h>
#include <kiway.h>
#include <lib_symbol.h>
#include <lib_symbol_library_manager.h>
#include <symbol_edit_frame.h>
#include <widgets/symbol_preview_widget.h>
#include <wx/filename.h>
#include <wx/msgdlg.h>
#include <wx/dataview.h>
DIALOG_IMPORT_SYMBOL_SELECT::DIALOG_IMPORT_SYMBOL_SELECT( SYMBOL_EDIT_FRAME* aParent,
const wxString& aFilePath,
const wxString& aDestLibrary,
SCH_IO_MGR::SCH_FILE_T aPluginType ) :
DIALOG_IMPORT_SYMBOL_SELECT_BASE( aParent ),
m_frame( aParent ),
m_filePath( aFilePath ),
m_destLibrary( aDestLibrary ),
m_plugin( SCH_IO_MGR::FindPlugin( aPluginType ) ),
m_preview( nullptr )
{
wxFileName fn( aFilePath );
SetTitle( wxString::Format( _( "Import Symbols from %s" ), fn.GetFullName() ) );
m_symbolList->AppendToggleColumn( wxEmptyString, wxDATAVIEW_CELL_ACTIVATABLE, 30 );
m_symbolList->AppendIconTextColumn( _( "Symbol" ), wxDATAVIEW_CELL_INERT, 250 );
m_symbolList->Connect( wxEVT_DATAVIEW_ITEM_VALUE_CHANGED,
wxDataViewEventHandler( DIALOG_IMPORT_SYMBOL_SELECT::onItemChecked ),
nullptr, this );
m_preview = new SYMBOL_PREVIEW_WIDGET( m_previewPanel, &Kiway(), false,
EDA_DRAW_PANEL_GAL::GAL_TYPE_OPENGL );
m_previewSizer->Add( m_preview, 1, wxEXPAND, 0 );
m_previewPanel->Layout();
m_unitChoice->Enable( false );
SetupStandardButtons( { { wxID_OK, _( "Import" ) } } );
m_sdbSizerOK->Disable();
SetInitialFocus( m_searchCtrl );
finishDialogSettings();
}
DIALOG_IMPORT_SYMBOL_SELECT::~DIALOG_IMPORT_SYMBOL_SELECT()
{
m_symbolList->Disconnect( wxEVT_DATAVIEW_ITEM_VALUE_CHANGED,
wxDataViewEventHandler( DIALOG_IMPORT_SYMBOL_SELECT::onItemChecked ),
nullptr, this );
}
bool DIALOG_IMPORT_SYMBOL_SELECT::TransferDataToWindow()
{
if( !loadSymbols() )
return false;
m_manager.BuildDependencyMaps();
refreshList();
updateStatusLine();
return true;
}
bool DIALOG_IMPORT_SYMBOL_SELECT::TransferDataFromWindow()
{
return resolveConflicts();
}
bool DIALOG_IMPORT_SYMBOL_SELECT::loadSymbols()
{
if( !m_plugin )
{
DisplayError( this, _( "Unable to find a plugin to read this library." ) );
return false;
}
wxArrayString symbolNames;
try
{
m_plugin->EnumerateSymbolLib( symbolNames, m_filePath );
}
catch( const IO_ERROR& ioe )
{
DisplayErrorMessage( this,
wxString::Format( _( "Cannot read symbol library '%s'." ), m_filePath ),
ioe.What() );
return false;
}
if( symbolNames.empty() )
{
DisplayError( this, wxString::Format( _( "Symbol library '%s' is empty." ), m_filePath ) );
return false;
}
// Get the library manager to check for existing symbols
LIB_SYMBOL_LIBRARY_MANAGER& libMgr = m_frame->GetLibManager();
m_manager.Clear();
for( const wxString& name : symbolNames )
{
wxString parentName;
bool isPower = false;
LIB_SYMBOL* sym = nullptr;
try
{
sym = m_plugin->LoadSymbol( m_filePath, name );
if( sym )
{
parentName = sym->GetParentName();
isPower = sym->IsPower();
}
}
catch( const IO_ERROR& )
{
// Symbol failed to load - still add it to list but without full info
}
// Add to manager - don't pass the symbol pointer since LoadSymbol returns
// a cached pointer owned by the plugin, not a new allocation
m_manager.AddSymbol( name, parentName, isPower, nullptr );
}
m_manager.CheckExistingSymbols(
[&libMgr, this]( const wxString& name ) {
return libMgr.SymbolExists( name, m_destLibrary );
} );
return true;
}
void DIALOG_IMPORT_SYMBOL_SELECT::refreshList()
{
m_symbolList->DeleteAllItems();
m_listIndices.clear();
int index = 0;
for( const wxString& name : m_manager.GetSymbolNames() )
{
if( !matchesFilter( name ) )
{
m_listIndices[name] = -1;
continue;
}
m_listIndices[name] = index;
const SYMBOL_IMPORT_INFO* info = m_manager.GetSymbolInfo( name );
if( !info )
continue;
wxVector<wxVariant> data;
wxIcon icon;
// Checkbox column - show checked for both manual and auto-selected
bool isChecked = info->m_checked || info->m_autoSelected;
data.push_back( wxVariant( isChecked ) );
if( info->m_isPower )
{
wxBitmap bmp = KiBitmap( BITMAPS::add_power );
icon.CopyFromBitmap( bmp );
}
else if( info->m_existsInDest )
{
wxBitmap bmp = KiBitmap( BITMAPS::small_warning );
icon.CopyFromBitmap( bmp );
}
else if( !info->m_parentName.IsEmpty() )
{
wxBitmap bmp = KiBitmap( BITMAPS::tree_nosel );
icon.CopyFromBitmap( bmp );
}
wxDataViewIconText iconText( name, icon );
data.push_back( wxVariant( iconText ) );
m_symbolList->AppendItem( data );
index++;
}
updateImportButton();
}
void DIALOG_IMPORT_SYMBOL_SELECT::updatePreview()
{
if( m_selectedSymbol.IsEmpty() )
{
m_preview->DisplayPart( nullptr, 0 );
m_unitChoice->Clear();
m_unitChoice->Enable( false );
return;
}
// Load symbol from plugin for preview (returns cached pointer)
LIB_SYMBOL* sym = nullptr;
try
{
sym = m_plugin->LoadSymbol( m_filePath, m_selectedSymbol );
}
catch( const IO_ERROR& )
{
m_preview->DisplayPart( nullptr, 0 );
return;
}
if( !sym )
{
m_preview->DisplayPart( nullptr, 0 );
return;
}
int unitCount = std::max( sym->GetUnitCount(), 1 );
// Update unit choice if count changed
m_unitChoice->Enable( unitCount > 1 );
m_unitChoice->Clear();
if( unitCount > 1 )
{
for( int ii = 0; ii < unitCount; ii++ )
m_unitChoice->Append( sym->GetUnitDisplayName( ii + 1, true ) );
m_unitChoice->SetSelection( 0 );
}
int selectedUnit = ( m_unitChoice->GetSelection() != wxNOT_FOUND )
? m_unitChoice->GetSelection() + 1
: 1;
// For derived symbols, we need to flatten to show properly
std::unique_ptr<LIB_SYMBOL> flattenedSym;
if( sym->IsDerived() || !sym->GetParentName().IsEmpty() )
{
try
{
flattenedSym = sym->Flatten();
m_preview->DisplayPart( flattenedSym.get(), selectedUnit );
}
catch( const IO_ERROR& )
{
// show unflattened symbol as fallback
m_preview->DisplayPart( sym, selectedUnit );
}
}
else
{
m_preview->DisplayPart( sym, selectedUnit );
}
}
void DIALOG_IMPORT_SYMBOL_SELECT::updateStatusLine()
{
int manualCount = m_manager.GetManualSelectionCount();
int autoCount = m_manager.GetAutoSelectionCount();
wxString status;
if( autoCount > 0 )
{
status = wxString::Format( _( "%d symbols selected, %d parents auto-included" ),
manualCount, autoCount );
}
else
{
status = wxString::Format( _( "%d symbols selected" ), manualCount );
}
m_statusLine->SetLabel( status );
}
void DIALOG_IMPORT_SYMBOL_SELECT::updateImportButton()
{
bool hasSelection = !m_manager.GetSymbolsToImport().empty();
m_sdbSizerOK->Enable( hasSelection );
}
void DIALOG_IMPORT_SYMBOL_SELECT::OnFilterTextChanged( wxCommandEvent& event )
{
m_filterString = m_searchCtrl->GetValue().Lower();
refreshList();
}
void DIALOG_IMPORT_SYMBOL_SELECT::OnSymbolSelected( wxDataViewEvent& event )
{
int row = m_symbolList->GetSelectedRow();
if( row == wxNOT_FOUND )
{
m_selectedSymbol.clear();
updatePreview();
return;
}
for( const auto& [name, listIndex] : m_listIndices )
{
if( listIndex == row )
{
m_selectedSymbol = name;
updatePreview();
return;
}
}
}
void DIALOG_IMPORT_SYMBOL_SELECT::onItemChecked( wxDataViewEvent& event )
{
if( event.GetColumn() != COL_CHECKBOX )
return;
int row = m_symbolList->ItemToRow( event.GetItem() );
if( row == wxNOT_FOUND )
return;
for( const auto& [name, listIndex] : m_listIndices )
{
if( listIndex == row )
{
wxVariant value;
m_symbolList->GetValue( value, row, COL_CHECKBOX );
bool newState = value.GetBool();
toggleSymbolSelection( name, newState );
return;
}
}
}
bool DIALOG_IMPORT_SYMBOL_SELECT::toggleSymbolSelection( const wxString& aSymbolName, bool aChecked )
{
if( aChecked )
{
std::vector<wxString> changed = m_manager.SetSymbolSelected( aSymbolName, true );
for( const wxString& changedName : changed )
{
auto it = m_listIndices.find( changedName );
if( it != m_listIndices.end() && it->second >= 0 )
{
m_symbolList->GetStore()->SetValueByRow( true, it->second, COL_CHECKBOX );
}
}
}
else
{
m_manager.DeselectWithDescendants( aSymbolName );
for( const wxString& name : m_manager.GetSymbolNames() )
{
auto it = m_listIndices.find( name );
if( it != m_listIndices.end() && it->second >= 0 )
{
const SYMBOL_IMPORT_INFO* info = m_manager.GetSymbolInfo( name );
if( info )
{
bool shouldBeChecked = info->m_checked || info->m_autoSelected;
m_symbolList->GetStore()->SetValueByRow( shouldBeChecked, it->second, COL_CHECKBOX );
}
}
}
}
updateStatusLine();
updateImportButton();
return true;
}
bool DIALOG_IMPORT_SYMBOL_SELECT::matchesFilter( const wxString& aSymbolName ) const
{
return SYMBOL_IMPORT_MANAGER::MatchesFilter( aSymbolName, m_filterString );
}
void DIALOG_IMPORT_SYMBOL_SELECT::OnSelectAll( wxCommandEvent& event )
{
m_manager.SelectAll( [this]( const wxString& name ) { return matchesFilter( name ); } );
refreshList();
updateStatusLine();
updateImportButton();
}
void DIALOG_IMPORT_SYMBOL_SELECT::OnSelectNone( wxCommandEvent& event )
{
m_manager.DeselectAll( [this]( const wxString& name ) { return matchesFilter( name ); } );
refreshList();
updateStatusLine();
updateImportButton();
}
void DIALOG_IMPORT_SYMBOL_SELECT::OnUnitChanged( wxCommandEvent& event )
{
if( m_selectedSymbol.IsEmpty() )
return;
int selectedUnit = ( m_unitChoice->GetSelection() != wxNOT_FOUND )
? m_unitChoice->GetSelection() + 1
: 1;
// Load symbol from plugin for preview (returns cached pointer)
LIB_SYMBOL* sym = nullptr;
try
{
sym = m_plugin->LoadSymbol( m_filePath, m_selectedSymbol );
}
catch( const IO_ERROR& )
{
return;
}
if( !sym )
return;
// For derived symbols, we need to flatten to show properly
std::unique_ptr<LIB_SYMBOL> flattenedSym;
if( sym->IsDerived() || !sym->GetParentName().IsEmpty() )
{
try
{
flattenedSym = sym->Flatten();
m_preview->DisplayPart( flattenedSym.get(), selectedUnit );
}
catch( const IO_ERROR& )
{
m_preview->DisplayPart( sym, selectedUnit );
}
}
else
{
m_preview->DisplayPart( sym, selectedUnit );
}
}
std::vector<wxString> DIALOG_IMPORT_SYMBOL_SELECT::GetSelectedSymbols() const
{
return m_manager.GetSymbolsToImport();
}
bool DIALOG_IMPORT_SYMBOL_SELECT::resolveConflicts()
{
m_conflictResolutions.clear();
std::vector<wxString> conflicts = m_manager.GetConflicts();
if( conflicts.empty() )
return true;
wxDialog dlg( this, wxID_ANY, _( "Resolve Import Conflicts" ),
wxDefaultPosition, wxSize( 500, 400 ),
wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER );
wxBoxSizer* mainSizer = new wxBoxSizer( wxVERTICAL );
wxStaticText* label = new wxStaticText( &dlg, wxID_ANY,
_( "The following symbols already exist in the destination library. "
"Choose how to handle each conflict:" ) );
label->Wrap( 450 );
mainSizer->Add( label, 0, wxALL | wxEXPAND, 10 );
wxDataViewListCtrl* conflictList = new wxDataViewListCtrl( &dlg, wxID_ANY );
conflictList->AppendTextColumn( _( "Symbol" ), wxDATAVIEW_CELL_INERT, 200 );
conflictList->AppendTextColumn( _( "Action" ), wxDATAVIEW_CELL_EDITABLE, 100 );
for( const wxString& name : conflicts )
{
wxVector<wxVariant> data;
data.push_back( wxVariant( name ) );
data.push_back( wxVariant( _( "Overwrite" ) ) );
conflictList->AppendItem( data );
m_conflictResolutions[name] = CONFLICT_RESOLUTION::OVERWRITE;
}
mainSizer->Add( conflictList, 1, wxALL | wxEXPAND, 10 );
wxBoxSizer* actionSizer = new wxBoxSizer( wxHORIZONTAL );
wxButton* skipAllBtn = new wxButton( &dlg, wxID_ANY, _( "Skip All" ) );
wxButton* overwriteAllBtn = new wxButton( &dlg, wxID_ANY, _( "Overwrite All" ) );
actionSizer->Add( skipAllBtn, 0, wxRIGHT, 5 );
actionSizer->Add( overwriteAllBtn, 0 );
mainSizer->Add( actionSizer, 0, wxLEFT | wxRIGHT | wxBOTTOM, 10 );
wxStdDialogButtonSizer* btnSizer = new wxStdDialogButtonSizer();
btnSizer->AddButton( new wxButton( &dlg, wxID_OK, _( "Import" ) ) );
btnSizer->AddButton( new wxButton( &dlg, wxID_CANCEL ) );
btnSizer->Realize();
mainSizer->Add( btnSizer, 0, wxALL | wxEXPAND, 10 );
dlg.SetSizer( mainSizer );
skipAllBtn->Bind( wxEVT_BUTTON, [&]( wxCommandEvent& ) {
for( size_t i = 0; i < conflicts.size(); i++ )
{
conflictList->SetTextValue( _( "Skip" ), i, 1 );
m_conflictResolutions[conflicts[i]] = CONFLICT_RESOLUTION::SKIP;
}
} );
overwriteAllBtn->Bind( wxEVT_BUTTON, [&]( wxCommandEvent& ) {
for( size_t i = 0; i < conflicts.size(); i++ )
{
conflictList->SetTextValue( _( "Overwrite" ), i, 1 );
m_conflictResolutions[conflicts[i]] = CONFLICT_RESOLUTION::OVERWRITE;
}
} );
conflictList->Bind( wxEVT_DATAVIEW_ITEM_VALUE_CHANGED, [&]( wxDataViewEvent& evt ) {
int row = conflictList->ItemToRow( evt.GetItem() );
if( row >= 0 && row < (int) conflicts.size() )
{
wxString action = conflictList->GetTextValue( row, 1 );
m_conflictResolutions[conflicts[row]] =
( action == _( "Skip" ) ) ? CONFLICT_RESOLUTION::SKIP
: CONFLICT_RESOLUTION::OVERWRITE;
}
} );
return dlg.ShowModal() == wxID_OK;
}