Files
kicad-source-mirror/eeschema/symbol_tree_model_adapter.cpp
Seth Hillbrand 0c10366e84 Fix symbol chooser flicker with missing libraries
Libraries with permanent load errors (LOAD_ERROR) were re-added to
m_pending_load_libraries on every timer tick, creating an infinite
retry loop. Each iteration called m_lazyLoadHandler() which triggered
Regenerate(), rebuilding the tree and resetting scroll position every
second.

Skip LOAD_ERROR libraries instead of retrying them, and only call the
lazy load handler when at least one library was actually loaded in the
current iteration.

Fixes https://gitlab.com/kicad/code/kicad/-/issues/23455
2026-03-16 12:16:45 -07:00

244 lines
8.5 KiB
C++

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2017 Chris Pavlina <pavlina.chris@gmail.com>
* Copyright (C) 2014 Henner Zeller <h.zeller@acm.org>
* 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 "symbol_tree_model_adapter.h"
#include <wx/log.h>
#include <wx/tokenzr.h>
#include <wx/window.h>
#include <core/kicad_algo.h>
#include <pgm_base.h>
#include <settings/common_settings.h>
#include <project/project_file.h>
#include <widgets/wx_progress_reporters.h>
#include <dialogs/html_message_box.h>
#include <generate_alias_info.h>
#include <sch_base_frame.h>
#include <locale_io.h>
#include <string_utils.h>
#include <trace_helpers.h>
#include <libraries/symbol_library_adapter.h>
wxObjectDataPtr<LIB_TREE_MODEL_ADAPTER>
SYMBOL_TREE_MODEL_ADAPTER::Create( SCH_BASE_FRAME* aParent, SYMBOL_LIBRARY_ADAPTER* aManager )
{
auto* adapter = new SYMBOL_TREE_MODEL_ADAPTER( aParent, aManager );
return wxObjectDataPtr<LIB_TREE_MODEL_ADAPTER>( adapter );
}
SYMBOL_TREE_MODEL_ADAPTER::SYMBOL_TREE_MODEL_ADAPTER( SCH_BASE_FRAME* aParent, SYMBOL_LIBRARY_ADAPTER* aLibs ) :
LIB_TREE_MODEL_ADAPTER( aParent, "pinned_symbol_libs", aParent->GetViewerSettingsBase()->m_LibTree ),
m_adapter( aLibs ),
m_check_pending_libraries_timer( nullptr )
{
m_colWidths[ GetDefaultFieldName( FIELD_T::VALUE, false ) ] = 300;
m_colWidths[ GetDefaultFieldName( FIELD_T::FOOTPRINT, false ) ] = 600;
m_availableColumns.emplace_back( GetDefaultFieldName( FIELD_T::VALUE, false ) );
m_availableColumns.emplace_back( GetDefaultFieldName( FIELD_T::FOOTPRINT, false ) );
}
SYMBOL_TREE_MODEL_ADAPTER::~SYMBOL_TREE_MODEL_ADAPTER()
{}
void SYMBOL_TREE_MODEL_ADAPTER::loadColumnConfig()
{
for( const std::pair<const wxString, int>& pair : m_cfg.column_widths )
m_colWidths[pair.first] = pair.second;
m_shownColumns = m_cfg.columns;
if( m_shownColumns.empty() )
m_shownColumns = { _HKI( "Item" ), _HKI( "Description" ), GetDefaultFieldName( FIELD_T::VALUE, false ) };
if( m_shownColumns[0] != _HKI( "Item" ) )
m_shownColumns.insert( m_shownColumns.begin(), _HKI( "Item" ) );
}
void SYMBOL_TREE_MODEL_ADAPTER::AddLibraries( SCH_BASE_FRAME* aFrame )
{
COMMON_SETTINGS* cfg = Pgm().GetCommonSettings();
PROJECT_FILE& project = aFrame->Prj().GetProjectFile();
std::unordered_set<wxString> pinned;
std::ranges::copy( cfg->m_Session.pinned_symbol_libs, std::inserter( pinned, pinned.begin() ) );
std::ranges::copy( project.m_PinnedSymbolLibs, std::inserter( pinned, pinned.begin() ) );
auto addFunc =
[&]( const wxString& aLibName, const std::vector<LIB_SYMBOL*>& aSymbolList,
const wxString& aDescription )
{
LIB_TREE_NODE_LIBRARY& lib_node =
DoAddLibraryNode( aLibName, aDescription, pinned.contains( aLibName ) );
for( LIB_TREE_ITEM* item: aSymbolList )
{
if( item )
lib_node.AddItem( item );
}
lib_node.AssignIntrinsicRanks( m_shownColumns, false );
};
LIBRARY_MANAGER& manager = Pgm().GetLibraryManager();
std::vector<wxString> toLoad;
std::ranges::copy( m_pending_load_libraries, std::back_inserter( toLoad ) );
bool isLazyLoad = !m_pending_load_libraries.empty();
m_pending_load_libraries.clear();
if( toLoad.empty() )
{
for( const LIBRARY_TABLE_ROW* row : manager.Rows( LIBRARY_TABLE_TYPE::SYMBOL ) )
{
if( row->Disabled() || row->Hidden() )
continue;
toLoad.emplace_back( row->Nickname() );
}
}
bool anyLoaded = false;
for( const wxString& lib : toLoad )
{
std::optional<LIB_STATUS> status = m_adapter->GetLibraryStatus( lib );
if( status && status->load_status == LOAD_STATUS::LOAD_ERROR )
continue;
if( !status || status->load_status != LOAD_STATUS::LOADED )
{
m_pending_load_libraries.insert( lib );
continue;
}
anyLoaded = true;
std::optional<const LIBRARY_TABLE_ROW*> rowResult = manager.GetRow( LIBRARY_TABLE_TYPE::SYMBOL, lib );
wxCHECK2( rowResult, continue );
wxString libDescription = ( *rowResult )->Description();
std::vector<LIB_SYMBOL*> libSymbols = m_adapter->GetSymbols( lib );
for( const wxString& column : m_adapter->GetAvailableExtraFields( lib ) )
addColumnIfNecessary( column );
if( m_adapter->SupportsSubLibraries( lib ) )
{
for( const auto& [nickname, description] : m_adapter->GetSubLibraries( lib ) )
{
wxString suffix = lib.IsEmpty() ? wxString( wxT( "" ) )
: wxString::Format( wxT( " - %s" ), nickname );
wxString name = wxString::Format( wxT( "%s%s" ), lib, suffix );
wxString desc = description;
if( !libDescription.IsEmpty() )
{
desc = wxString::Format( wxT( "%s (%s)" ),
libDescription,
desc.IsEmpty() ? lib : desc );
}
UTF8 utf8Lib( nickname );
std::vector<LIB_SYMBOL*> symbols;
std::copy_if( libSymbols.begin(), libSymbols.end(),
std::back_inserter( symbols ),
[&utf8Lib]( LIB_SYMBOL* aSym )
{
return utf8Lib == aSym->GetLibId().GetSubLibraryName();
} );
addFunc( name, symbols, desc );
}
}
else
{
addFunc( lib, libSymbols, libDescription );
}
}
if( !m_pending_load_libraries.empty() && !m_check_pending_libraries_timer )
{
m_check_pending_libraries_timer = std::make_unique<wxTimer>( aFrame );
wxLogTrace( traceLibraries, "%zu pending libraries, starting lazy load...", m_pending_load_libraries.size() );
aFrame->Bind( wxEVT_TIMER,
[&, aFrame]( wxTimerEvent& )
{
AddLibraries( aFrame );
if( m_pending_load_libraries.empty() )
{
m_check_pending_libraries_timer->Stop();
m_check_pending_libraries_timer.reset();
wxLogTrace( traceLibraries, "Done lazy-loading libraries" );
}
},
m_check_pending_libraries_timer->GetId() );
m_check_pending_libraries_timer->Start( 1000 );
}
m_tree.AssignIntrinsicRanks( m_shownColumns );
if( isLazyLoad && anyLoaded && m_lazyLoadHandler )
{
createMissingColumns();
m_lazyLoadHandler();
}
}
void SYMBOL_TREE_MODEL_ADAPTER::AddLibrary( wxString const& aLibNickname, bool pinned )
{
SYMBOL_LIBRARY_ADAPTER::SYMBOL_TYPE type = GetFilter() ? SYMBOL_LIBRARY_ADAPTER::SYMBOL_TYPE::POWER_ONLY
: SYMBOL_LIBRARY_ADAPTER::SYMBOL_TYPE::ALL_SYMBOLS;
std::vector<LIB_SYMBOL*> symbols = m_adapter->GetSymbols( aLibNickname, type );
std::optional<const LIBRARY_TABLE_ROW*> row = Pgm().GetLibraryManager().GetRow( LIBRARY_TABLE_TYPE::SYMBOL,
aLibNickname );
std::vector<LIB_TREE_ITEM*> comp_list;
if( row && symbols.size() > 0 )
{
comp_list.assign( symbols.begin(), symbols.end() );
DoAddLibrary( aLibNickname, ( *row )->Description(), comp_list, pinned, false );
}
}
wxString SYMBOL_TREE_MODEL_ADAPTER::GenerateInfo( LIB_ID const& aLibId, int aUnit )
{
return GenerateAliasInfo( m_adapter, aLibId, aUnit );
}