mirror of
https://gitlab.com/kicad/code/kicad.git
synced 2025-09-14 18:23:15 +02:00
Recommendation is to avoid using the year nomenclature as this information is already encoded in the git repo. Avoids needing to repeatly update. Also updates AUTHORS.txt from current repo with contributor names
386 lines
11 KiB
C++
386 lines
11 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 (C) 2023 CERN
|
|
* 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 <lib_tree_model.h>
|
|
|
|
#include <algorithm>
|
|
#include <eda_pattern_match.h>
|
|
#include <lib_tree_item.h>
|
|
#include <pgm_base.h>
|
|
#include <string_utils.h>
|
|
|
|
|
|
|
|
void LIB_TREE_NODE::ResetScore()
|
|
{
|
|
for( std::unique_ptr<LIB_TREE_NODE>& child: m_Children )
|
|
child->ResetScore();
|
|
|
|
m_Score = 0;
|
|
}
|
|
|
|
|
|
void LIB_TREE_NODE::AssignIntrinsicRanks( bool presorted )
|
|
{
|
|
std::vector<LIB_TREE_NODE*> sort_buf;
|
|
|
|
if( presorted )
|
|
{
|
|
int max = m_Children.size() - 1;
|
|
|
|
for( int i = 0; i <= max; ++i )
|
|
m_Children[i]->m_IntrinsicRank = max - i;
|
|
}
|
|
else
|
|
{
|
|
for( std::unique_ptr<LIB_TREE_NODE>& child: m_Children )
|
|
sort_buf.push_back( child.get() );
|
|
|
|
std::sort( sort_buf.begin(), sort_buf.end(),
|
|
[]( LIB_TREE_NODE* a, LIB_TREE_NODE* b ) -> bool
|
|
{
|
|
return StrNumCmp( a->m_Name, b->m_Name, true ) > 0;
|
|
} );
|
|
|
|
for( int i = 0; i < (int) sort_buf.size(); ++i )
|
|
sort_buf[i]->m_IntrinsicRank = i;
|
|
}
|
|
}
|
|
|
|
|
|
void LIB_TREE_NODE::SortNodes( bool aUseScores )
|
|
{
|
|
std::sort( m_Children.begin(), m_Children.end(),
|
|
[&]( std::unique_ptr<LIB_TREE_NODE>& a, std::unique_ptr<LIB_TREE_NODE>& b )
|
|
{
|
|
return Compare( *a, *b, aUseScores );
|
|
} );
|
|
|
|
for( std::unique_ptr<LIB_TREE_NODE>& node: m_Children )
|
|
node->SortNodes( aUseScores );
|
|
}
|
|
|
|
|
|
bool LIB_TREE_NODE::Compare( LIB_TREE_NODE const& aNode1, LIB_TREE_NODE const& aNode2,
|
|
bool aUseScores )
|
|
{
|
|
if( aNode1.m_Type != aNode2.m_Type )
|
|
return aNode1.m_Type < aNode2.m_Type;
|
|
|
|
// Recently used sorts at top
|
|
if( aNode1.m_Name.StartsWith( wxT( "-- " ) ) )
|
|
{
|
|
if( aNode2.m_Name.StartsWith( wxT( "-- " ) ) )
|
|
{
|
|
// Make sure -- Recently Used is always at the top
|
|
// Start by checking the name of aNode2, because we
|
|
// want to satisfy the irreflexive property of the
|
|
// strict weak ordering.
|
|
if( aNode2.m_Name.StartsWith( wxT( "-- Recently Used" ) ) )
|
|
return false;
|
|
else if( aNode1.m_Name.StartsWith( wxT( "-- Recently Used" ) ) )
|
|
return true;
|
|
|
|
return aNode1.m_IntrinsicRank > aNode2.m_IntrinsicRank;
|
|
}
|
|
else
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
else if( aNode2.m_Name.StartsWith( wxT( "-- " ) ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Pinned nodes go next
|
|
if( aNode1.m_Pinned && !aNode2.m_Pinned )
|
|
return true;
|
|
else if( aNode2.m_Pinned && !aNode1.m_Pinned )
|
|
return false;
|
|
|
|
if( aUseScores && aNode1.m_Score != aNode2.m_Score )
|
|
return aNode1.m_Score > aNode2.m_Score;
|
|
|
|
if( aNode1.m_IntrinsicRank != aNode2.m_IntrinsicRank )
|
|
return aNode1.m_IntrinsicRank > aNode2.m_IntrinsicRank;
|
|
|
|
return reinterpret_cast<const void*>( &aNode1 ) < reinterpret_cast<const void*>( &aNode2 );
|
|
}
|
|
|
|
|
|
LIB_TREE_NODE::LIB_TREE_NODE()
|
|
: m_Parent( nullptr ),
|
|
m_Type( TYPE::INVALID ),
|
|
m_IntrinsicRank( 0 ),
|
|
m_Score( 0 ),
|
|
m_Pinned( false ),
|
|
m_PinCount( 0 ),
|
|
m_Unit( 0 ),
|
|
m_IsRoot( false )
|
|
{}
|
|
|
|
|
|
LIB_TREE_NODE_UNIT::LIB_TREE_NODE_UNIT( LIB_TREE_NODE* aParent, LIB_TREE_ITEM* aItem, int aUnit )
|
|
{
|
|
static void* locale = nullptr;
|
|
static wxString namePrefix;
|
|
|
|
// Fetching translations can take a surprising amount of time when loading libraries,
|
|
// so only do it when necessary.
|
|
if( Pgm().GetLocale() != locale )
|
|
{
|
|
namePrefix = _( "Unit" );
|
|
locale = Pgm().GetLocale();
|
|
}
|
|
|
|
m_Parent = aParent;
|
|
m_Type = TYPE::UNIT;
|
|
|
|
m_Unit = aUnit;
|
|
m_LibId = aParent->m_LibId;
|
|
|
|
m_Name = namePrefix + " " + aItem->GetUnitReference( aUnit );
|
|
|
|
if( aItem->HasUnitDisplayName( aUnit ) )
|
|
m_Desc = aItem->GetUnitDisplayName( aUnit );
|
|
else
|
|
m_Desc = wxEmptyString;
|
|
|
|
m_IntrinsicRank = -aUnit;
|
|
}
|
|
|
|
|
|
void LIB_TREE_NODE_UNIT::UpdateScore( EDA_COMBINED_MATCHER* aMatcher, const wxString& aLib,
|
|
std::function<bool( LIB_TREE_NODE& aNode )>* aFilter )
|
|
{
|
|
// aMatcher test results are inherited from parent
|
|
if( aMatcher )
|
|
m_Score = m_Parent->m_Score;
|
|
|
|
// aFilter test is subtractive
|
|
if( aFilter && !(*aFilter)(*this) )
|
|
m_Score = 0;
|
|
|
|
// show all nodes if no search/filter/etc. criteria are given
|
|
if( !aMatcher && aLib.IsEmpty() && ( !aFilter || (*aFilter)(*this) ) )
|
|
m_Score = 1;
|
|
}
|
|
|
|
|
|
LIB_TREE_NODE_ITEM::LIB_TREE_NODE_ITEM( LIB_TREE_NODE* aParent, LIB_TREE_ITEM* aItem )
|
|
{
|
|
m_Type = TYPE::ITEM;
|
|
m_Parent = aParent;
|
|
|
|
m_LibId.SetLibNickname( aItem->GetLibNickname() );
|
|
m_LibId.SetLibItemName( aItem->GetName() );
|
|
|
|
m_Name = aItem->GetName();
|
|
m_Desc = aItem->GetDesc();
|
|
m_Footprint = aItem->GetFootprint();
|
|
m_PinCount = aItem->GetPinCount();
|
|
|
|
aItem->GetChooserFields( m_Fields );
|
|
|
|
m_SearchTerms = aItem->GetSearchTerms();
|
|
|
|
m_IsRoot = aItem->IsRoot();
|
|
|
|
if( aItem->GetSubUnitCount() > 1 )
|
|
{
|
|
for( int u = 1; u <= aItem->GetSubUnitCount(); ++u )
|
|
AddUnit( aItem, u );
|
|
}
|
|
}
|
|
|
|
|
|
LIB_TREE_NODE_UNIT& LIB_TREE_NODE_ITEM::AddUnit( LIB_TREE_ITEM* aItem, int aUnit )
|
|
{
|
|
LIB_TREE_NODE_UNIT* unit = new LIB_TREE_NODE_UNIT( this, aItem, aUnit );
|
|
m_Children.push_back( std::unique_ptr<LIB_TREE_NODE>( unit ) );
|
|
return *unit;
|
|
}
|
|
|
|
|
|
void LIB_TREE_NODE_ITEM::Update( LIB_TREE_ITEM* aItem )
|
|
{
|
|
m_LibId.SetLibNickname( aItem->GetLIB_ID().GetLibNickname() );
|
|
m_LibId.SetLibItemName( aItem->GetName() );
|
|
|
|
m_Name = aItem->GetName();
|
|
m_Desc = aItem->GetDesc();
|
|
|
|
aItem->GetChooserFields( m_Fields );
|
|
|
|
m_SearchTerms = aItem->GetSearchTerms();
|
|
|
|
m_IsRoot = aItem->IsRoot();
|
|
m_Children.clear();
|
|
|
|
for( int u = 1; u <= aItem->GetSubUnitCount(); ++u )
|
|
AddUnit( aItem, u );
|
|
}
|
|
|
|
|
|
void LIB_TREE_NODE_ITEM::UpdateScore( EDA_COMBINED_MATCHER* aMatcher, const wxString& aLib,
|
|
std::function<bool( LIB_TREE_NODE& aNode )>* aFilter )
|
|
{
|
|
// aMatcher test is additive, but if we don't match the given term at all, it nulls out
|
|
if( aMatcher )
|
|
{
|
|
int currentScore = aMatcher->ScoreTerms( m_SearchTerms );
|
|
|
|
// This is a hack: the second phase of search in the adapter will look for a tokenized
|
|
// LIB_ID and send the lib part down here. While we generally want to prune ourselves
|
|
// out here (by setting score to -1) the first time we fail to match a search term,
|
|
// we want to give the same search term a second chance if it has been split from a library
|
|
// name.
|
|
if( ( m_Score >= 0 || !aLib.IsEmpty() ) && currentScore > 0 )
|
|
m_Score += currentScore;
|
|
else
|
|
m_Score = -1; // Item has failed to match this term, rule it out
|
|
}
|
|
|
|
// aFilter test is subtractive
|
|
if( aFilter && !(*aFilter)(*this) )
|
|
m_Score = 0;
|
|
|
|
// show all nodes if no search/filter/etc. criteria are given
|
|
if( !aMatcher && aLib.IsEmpty() && ( !aFilter || (*aFilter)(*this) ) )
|
|
m_Score = 1;
|
|
|
|
for( std::unique_ptr<LIB_TREE_NODE>& child: m_Children )
|
|
child->UpdateScore( aMatcher, aLib, aFilter );
|
|
}
|
|
|
|
|
|
LIB_TREE_NODE_LIBRARY::LIB_TREE_NODE_LIBRARY( LIB_TREE_NODE* aParent, wxString const& aName,
|
|
wxString const& aDesc )
|
|
{
|
|
m_Type = TYPE::LIBRARY;
|
|
m_Name = aName;
|
|
m_Desc = aDesc;
|
|
m_Parent = aParent;
|
|
m_LibId.SetLibNickname( aName );
|
|
|
|
m_SearchTerms.emplace_back( SEARCH_TERM( aName, 8 ) );
|
|
}
|
|
|
|
|
|
LIB_TREE_NODE_ITEM& LIB_TREE_NODE_LIBRARY::AddItem( LIB_TREE_ITEM* aItem )
|
|
{
|
|
LIB_TREE_NODE_ITEM* item = new LIB_TREE_NODE_ITEM( this, aItem );
|
|
m_Children.push_back( std::unique_ptr<LIB_TREE_NODE>( item ) );
|
|
return *item;
|
|
}
|
|
|
|
|
|
void LIB_TREE_NODE_LIBRARY::UpdateScore( EDA_COMBINED_MATCHER* aMatcher, const wxString& aLib,
|
|
std::function<bool( LIB_TREE_NODE& aNode )>* aFilter )
|
|
{
|
|
int maxChildScore = 0;
|
|
|
|
// children are additive
|
|
if( !m_Children.empty() )
|
|
{
|
|
for( std::unique_ptr<LIB_TREE_NODE>& child: m_Children )
|
|
{
|
|
child->UpdateScore( aMatcher, aLib, aFilter );
|
|
maxChildScore = std::max( maxChildScore, child->m_Score );
|
|
}
|
|
|
|
m_Score = std::max( m_Score, maxChildScore );
|
|
}
|
|
|
|
// aLib test is additive
|
|
if( !aLib.IsEmpty() && m_Name.Lower().Matches( aLib ) )
|
|
m_Score += 1;
|
|
|
|
// aMatcher test is additive
|
|
if( aMatcher )
|
|
{
|
|
int ownScore = aMatcher->ScoreTerms( m_SearchTerms );
|
|
m_Score += ownScore;
|
|
|
|
// If we have a hit on a library, show all children in that library that pass the filter
|
|
if( maxChildScore <= 0 && ownScore > 0 )
|
|
{
|
|
for( std::unique_ptr<LIB_TREE_NODE>& child: m_Children )
|
|
{
|
|
if( !aFilter || (*aFilter)( *this ) )
|
|
child->ForceScore( 1 );
|
|
|
|
maxChildScore = std::max( maxChildScore, child->m_Score );
|
|
}
|
|
}
|
|
}
|
|
|
|
// If all children are excluded for one reason or another, drop the library from the list
|
|
if( !m_Children.empty() && maxChildScore <= 0 )
|
|
m_Score = 0;
|
|
|
|
// show all nodes if no search/filter/etc. criteria are given
|
|
if( m_Children.empty() && !aMatcher && aLib.IsEmpty() && ( !aFilter || (*aFilter)(*this) ) )
|
|
m_Score = 1;
|
|
}
|
|
|
|
|
|
LIB_TREE_NODE_ROOT::LIB_TREE_NODE_ROOT()
|
|
{
|
|
m_Type = TYPE::ROOT;
|
|
}
|
|
|
|
|
|
LIB_TREE_NODE_LIBRARY& LIB_TREE_NODE_ROOT::AddLib( wxString const& aName, wxString const& aDesc )
|
|
{
|
|
LIB_TREE_NODE_LIBRARY* lib = new LIB_TREE_NODE_LIBRARY( this, aName, aDesc );
|
|
m_Children.push_back( std::unique_ptr<LIB_TREE_NODE>( lib ) );
|
|
return *lib;
|
|
}
|
|
|
|
|
|
void LIB_TREE_NODE_ROOT::RemoveLib( wxString const& aName )
|
|
{
|
|
m_Children.erase( std::remove_if( m_Children.begin(), m_Children.end(),
|
|
[&]( std::unique_ptr<LIB_TREE_NODE>& aNode )
|
|
{
|
|
return aNode->m_Name == aName;
|
|
} ),
|
|
m_Children.end() );
|
|
}
|
|
|
|
|
|
void LIB_TREE_NODE_ROOT::Clear()
|
|
{
|
|
m_Children.clear();
|
|
}
|
|
|
|
|
|
void LIB_TREE_NODE_ROOT::UpdateScore( EDA_COMBINED_MATCHER* aMatcher, const wxString& aLib,
|
|
std::function<bool( LIB_TREE_NODE& aNode )>* aFilter )
|
|
{
|
|
for( std::unique_ptr<LIB_TREE_NODE>& child: m_Children )
|
|
child->UpdateScore( aMatcher, aLib, aFilter );
|
|
}
|
|
|