/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2017 Chris Pavlina * Copyright (C) 2014 Henner Zeller * Copyright (C) 2023 CERN * Copyright (C) 2014-2023 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 . */ #include #include #include #include #include #include void LIB_TREE_NODE::ResetScore() { for( std::unique_ptr& child: m_Children ) child->ResetScore(); m_Score = 0; } void LIB_TREE_NODE::AssignIntrinsicRanks( bool presorted ) { std::vector 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& 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& a, std::unique_ptr& b ) { return Compare( *a, *b, aUseScores ); } ); for( std::unique_ptr& 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 if( aNode1.m_Name.StartsWith( wxT( "-- Recently Used" ) ) ) return true; else if( aNode2.m_Name.StartsWith( wxT( "-- Recently Used" ) ) ) return false; 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( &aNode1 ) < reinterpret_cast( &aNode2 ); } LIB_TREE_NODE::LIB_TREE_NODE() : m_Parent( nullptr ), m_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 = 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* 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 = ITEM; m_Parent = aParent; m_LibId.SetLibNickname( aItem->GetLibNickname() ); m_LibId.SetLibItemName( aItem->GetName() ); m_Name = aItem->GetName(); m_Desc = aItem->GetDescription(); m_Footprint = aItem->GetFootprint(); m_PinCount = aItem->GetPinCount(); aItem->GetChooserFields( m_Fields ); m_SearchTerms = aItem->GetSearchTerms(); m_IsRoot = aItem->IsRoot(); if( aItem->GetUnitCount() > 1 ) { for( int u = 1; u <= aItem->GetUnitCount(); ++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( unit ) ); return *unit; } void LIB_TREE_NODE_ITEM::Update( LIB_TREE_ITEM* aItem ) { m_LibId.SetLibNickname( aItem->GetLibId().GetLibNickname() ); m_LibId.SetLibItemName( aItem->GetName() ); m_Name = aItem->GetName(); m_Desc = aItem->GetDescription(); aItem->GetChooserFields( m_Fields ); m_SearchTerms = aItem->GetSearchTerms(); m_IsRoot = aItem->IsRoot(); m_Children.clear(); for( int u = 1; u <= aItem->GetUnitCount(); ++u ) AddUnit( aItem, u ); } void LIB_TREE_NODE_ITEM::UpdateScore( EDA_COMBINED_MATCHER* aMatcher, const wxString& aLib, std::function* 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& 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 = 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( item ) ); return *item; } void LIB_TREE_NODE_LIBRARY::UpdateScore( EDA_COMBINED_MATCHER* aMatcher, const wxString& aLib, std::function* aFilter ) { int newScore = 0; for( std::unique_ptr& child: m_Children ) { child->UpdateScore( aMatcher, aLib, aFilter ); newScore = std::max( newScore, child->m_Score ); } // Each time UpdateScore is called for a library, child (item) scores may go up or down. // If the all go down to zero, we need to make sure to drop the library from the list. if( newScore > 0 ) m_Score = std::max( m_Score, newScore ); else m_Score = 0; // aLib test is additive, but only when we've already accumulated some score from children if( !aLib.IsEmpty() && m_Name.Lower().Matches( aLib ) && ( m_Score > 0 || m_Children.empty() ) ) { m_Score += 1; } // aMatcher test is additive if( aMatcher ) m_Score += aMatcher->ScoreTerms( m_SearchTerms ); // 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 = 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 ) ); return *lib; } void LIB_TREE_NODE_ROOT::UpdateScore( EDA_COMBINED_MATCHER* aMatcher, const wxString& aLib, std::function* aFilter ) { for( std::unique_ptr& child: m_Children ) child->UpdateScore( aMatcher, aLib, aFilter ); }