/* * 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) 2014-2018 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 // Each node gets this lowest score initially, without any matches applied. // Matches will then increase this score depending on match quality. This way, // an empty search string will result in all components being displayed as they // have the minimum score. However, in that case, we avoid expanding all the // nodes asd the result is very unspecific. static const unsigned kLowestDefaultScore = 1; // Creates a score depending on the position of a string match. If the position // is 0 (= prefix match), this returns the maximum score. This degrades until // pos == max, which returns a score of 0; Evertyhing else beyond that is just // 0. Only values >= 0 allowed for position and max. // // @param aPosition is the position a string has been found in a substring. // @param aMaximum is the maximum score this function returns. // @return position dependent score. static int matchPosScore(int aPosition, int aMaximum) { return ( aPosition < aMaximum ) ? aMaximum - aPosition : 0; } void CMP_TREE_NODE::ResetScore() { for( auto& child: Children ) child->ResetScore(); Score = kLowestDefaultScore; } void CMP_TREE_NODE::AssignIntrinsicRanks() { std::vector sort_buf; for( auto const& node: Children ) sort_buf.push_back( &*node ); std::sort( sort_buf.begin(), sort_buf.end(), []( CMP_TREE_NODE* a, CMP_TREE_NODE* b ) -> bool { return a->MatchName > b->MatchName; } ); for( int i = 0; i < (int) sort_buf.size(); ++i ) sort_buf[i]->IntrinsicRank = i; } void CMP_TREE_NODE::SortNodes() { std::sort( Children.begin(), Children.end(), []( std::unique_ptr const& a, std::unique_ptr const& b ) { return Compare( *a, *b ) > 0; } ); for( auto& node: Children ) { node->SortNodes(); } } int CMP_TREE_NODE::Compare( CMP_TREE_NODE const& aNode1, CMP_TREE_NODE const& aNode2 ) { if( aNode1.Type != aNode2.Type ) return 0; if( aNode1.Score != aNode2.Score ) return aNode1.Score - aNode2.Score; if( aNode1.Parent != aNode2.Parent ) return 0; return aNode1.IntrinsicRank - aNode2.IntrinsicRank; } CMP_TREE_NODE::CMP_TREE_NODE() : Parent( nullptr ), Type( INVALID ), IntrinsicRank( 0 ), Score( kLowestDefaultScore ), SearchTextNormalized( false ), Unit( 0 ), IsRoot( false ) {} CMP_TREE_NODE_UNIT::CMP_TREE_NODE_UNIT( CMP_TREE_NODE* aParent, 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(); } Parent = aParent; Type = UNIT; Unit = aUnit; LibId = aParent->LibId; Name = namePrefix + " " + LIB_PART::SubReference( aUnit, false ); Desc = wxEmptyString; MatchName = wxEmptyString; IntrinsicRank = -aUnit; } CMP_TREE_NODE_LIB_ID::CMP_TREE_NODE_LIB_ID( CMP_TREE_NODE* aParent, LIB_ALIAS* aAlias ) { wxASSERT( aParent && aAlias ); Type = LIBID; Parent = aParent; Update( aAlias ); } CMP_TREE_NODE_UNIT& CMP_TREE_NODE_LIB_ID::AddUnit( int aUnit ) { CMP_TREE_NODE_UNIT* unit = new CMP_TREE_NODE_UNIT( this, aUnit ); Children.push_back( std::unique_ptr( unit ) ); return *unit; } void CMP_TREE_NODE_LIB_ID::Update( LIB_ALIAS* aAlias ) { Name = aAlias->GetName(); Desc = aAlias->GetDescription(); // Parent node is the library nickname so set the LIB_ID library nickname. IsRoot = aAlias->IsRoot(); // Pre-normalized strings for fast case-insensitive matching // Search text spaces out keywords and description to penalize description // matches - earlier matches are worth more. MatchName = aAlias->GetName().Lower(); SearchText = (aAlias->GetKeyWords() + " " + Desc); // Extract default footprint text LIB_PART* part = aAlias->GetPart(); wxString footprint; if( part ) { LibId = part->GetLibId(); LibId.SetLibItemName( Name ); footprint = part->GetFootprintField().GetText(); } // If a footprint is defined for the part, // add it to the serach string if( !footprint.IsEmpty() ) { SearchText += " "; SearchText += footprint; } Children.clear(); if( part && part->IsMulti() ) { for( int u = 1; u <= part->GetUnitCount(); ++u ) AddUnit( u ); } SearchTextNormalized = false; } void CMP_TREE_NODE_LIB_ID::UpdateScore( EDA_COMBINED_MATCHER& aMatcher ) { if( Score <= 0 ) return; // Leaf nodes without scores are out of the game. if( !SearchTextNormalized ) { SearchText = SearchText.Lower(); SearchTextNormalized = true; } // Keywords and description we only count if the match string is at // least two characters long. That avoids spurious, low quality // matches. Most abbreviations are at three characters long. int found_pos = EDA_PATTERN_NOT_FOUND; int matchers_fired = 0; if( aMatcher.GetPattern() == MatchName ) { Score += 1000; // exact match. High score :) } else if( aMatcher.Find( MatchName, matchers_fired, found_pos ) ) { // Substring match. The earlier in the string the better. Score += matchPosScore( found_pos, 20 ) + 20; } else if( aMatcher.Find( Parent->MatchName, matchers_fired, found_pos ) ) { Score += 19; // parent name matches. score += 19 } else if( aMatcher.Find( SearchText, matchers_fired, found_pos ) ) { // If we have a very short search term (like one or two letters), // we don't want to accumulate scores if they just happen to be in // keywords or description as almost any one or two-letter // combination shows up in there. if( aMatcher.GetPattern().length() >= 2 ) { // For longer terms, we add scores 1..18 for positional match // (higher in the front, where the keywords are). Score += matchPosScore( found_pos, 17 ) + 1; } } else { // No match. That's it for this item. Score = 0; } // More matchers = better match Score += 2 * matchers_fired; } CMP_TREE_NODE_LIB::CMP_TREE_NODE_LIB( CMP_TREE_NODE* aParent, wxString const& aName, wxString const& aDesc ) { Type = LIB; Name = aName; MatchName = aName.Lower(); Desc = aDesc; Parent = aParent; LibId.SetLibNickname( aName ); } CMP_TREE_NODE_LIB_ID& CMP_TREE_NODE_LIB::AddAlias( LIB_ALIAS* aAlias ) { CMP_TREE_NODE_LIB_ID* alias = new CMP_TREE_NODE_LIB_ID( this, aAlias ); Children.push_back( std::unique_ptr( alias ) ); return *alias; } void CMP_TREE_NODE_LIB::UpdateScore( EDA_COMBINED_MATCHER& aMatcher ) { Score = 0; for( auto& child: Children ) { child->UpdateScore( aMatcher ); Score = std::max( Score, child->Score ); } } CMP_TREE_NODE_ROOT::CMP_TREE_NODE_ROOT() { Type = ROOT; } CMP_TREE_NODE_LIB& CMP_TREE_NODE_ROOT::AddLib( wxString const& aName, wxString const& aDesc ) { CMP_TREE_NODE_LIB* lib = new CMP_TREE_NODE_LIB( this, aName, aDesc ); Children.push_back( std::unique_ptr( lib ) ); return *lib; } void CMP_TREE_NODE_ROOT::UpdateScore( EDA_COMBINED_MATCHER& aMatcher ) { for( auto& child: Children ) child->UpdateScore( aMatcher ); }