From df7d62fdd3c7b5b24522a48e21d3d34debdb5af5 Mon Sep 17 00:00:00 2001 From: Jeff Young Date: Mon, 1 May 2023 21:26:29 +0100 Subject: [PATCH] Added a sort-order dropdown to lib-tree filters. Also simplifies the scoring algorithm so that it only differentiates between exact-match, match-at-start and any-match. The rest of the position-based matching stuff is gone, as is the knowledge of the name vs the keywords vs the description. All that is left to the provider of the weighted search terms array. --- common/eda_pattern_match.cpp | 32 +++++ common/footprint_filter.cpp | 8 +- common/footprint_info.cpp | 20 +++ common/lib_tree_model.cpp | 120 +++--------------- common/lib_tree_model_adapter.cpp | 5 +- common/widgets/lib_tree.cpp | 38 +++++- eeschema/dialogs/dialog_choose_symbol.cpp | 6 +- eeschema/eeschema_settings.cpp | 3 + eeschema/eeschema_settings.h | 1 + eeschema/lib_symbol.cpp | 34 +++-- eeschema/lib_symbol.h | 2 +- .../sch_plugins/legacy/sch_legacy_plugin.cpp | 2 +- eeschema/symbol_editor/symbol_edit_frame.cpp | 3 + .../symbol_editor/symbol_editor_settings.cpp | 5 +- .../symbol_editor/symbol_editor_settings.h | 4 +- eeschema/symbol_viewer_frame.cpp | 12 +- include/eda_pattern_match.h | 22 ++++ include/footprint_editor_settings.h | 2 + include/footprint_info.h | 11 +- include/lib_tree_item.h | 5 +- include/lib_tree_model.h | 27 ++-- include/lib_tree_model_adapter.h | 11 +- include/widgets/lib_tree.h | 28 ++-- pcbnew/dialogs/dialog_choose_footprint.cpp | 48 ++++--- pcbnew/footprint_edit_frame.cpp | 4 + pcbnew/footprint_editor_settings.cpp | 3 + pcbnew/footprint_viewer_frame.cpp | 12 +- pcbnew/pcbnew_settings.cpp | 5 +- pcbnew/pcbnew_settings.h | 3 +- 29 files changed, 273 insertions(+), 203 deletions(-) diff --git a/common/eda_pattern_match.cpp b/common/eda_pattern_match.cpp index 65e1b8daf0..98ea454c77 100644 --- a/common/eda_pattern_match.cpp +++ b/common/eda_pattern_match.cpp @@ -445,6 +445,38 @@ bool EDA_COMBINED_MATCHER::StartsWith( const wxString& aTerm ) } +int EDA_COMBINED_MATCHER::ScoreTerms( std::vector& aWeightedTerms ) +{ + int score = 0; + + for( SEARCH_TERM& term : aWeightedTerms ) + { + if( !term.Normalized ) + { + term.Text = term.Text.MakeLower().Trim( false ).Trim( true ); + term.Normalized = true; + } + + int found_pos = EDA_PATTERN_NOT_FOUND; + int matchers_fired = 0; + + if( GetPattern() == term.Text ) + { + score += 8 * term.Score; + } + else if( Find( term.Text, matchers_fired, found_pos ) ) + { + if( found_pos == 0 ) + score += 2 * term.Score; + else + score += term.Score; + } + } + + return score; +} + + wxString const& EDA_COMBINED_MATCHER::GetPattern() const { return m_pattern; diff --git a/common/footprint_filter.cpp b/common/footprint_filter.cpp index fba0edfeca..f2bcfe2328 100644 --- a/common/footprint_filter.cpp +++ b/common/footprint_filter.cpp @@ -82,15 +82,13 @@ void FOOTPRINT_FILTER_IT::increment() if( ( filter_type & FOOTPRINT_FILTER::FILTERING_BY_TEXT_PATTERN ) ) { - wxString searchStr = wxString::Format( wxT( "%s:%s %s" ), - candidate.GetLibNickname(), - candidate.GetFootprintName(), - candidate.GetSearchText() ); bool exclude = false; for( std::unique_ptr& matcher : m_filter->m_pattern_filters ) { - if( !matcher->Find( searchStr.Lower() ) ) + std::vector searchTerms = candidate.GetSearchTerms(); + + if( !matcher->ScoreTerms( searchTerms ) ) { exclude = true; break; diff --git a/common/footprint_info.cpp b/common/footprint_info.cpp index 7909a84374..59a4db08b8 100644 --- a/common/footprint_info.cpp +++ b/common/footprint_info.cpp @@ -37,6 +37,7 @@ #include #include #include +#include #include FOOTPRINT_INFO* FOOTPRINT_LIST::GetFootprintInfo( const wxString& aLibNickname, @@ -69,6 +70,25 @@ FOOTPRINT_INFO* FOOTPRINT_LIST::GetFootprintInfo( const wxString& aFootprintName } +std::vector FOOTPRINT_INFO::GetSearchTerms() +{ + std::vector terms; + + terms.emplace_back( SEARCH_TERM( GetName(), 8 ) ); + + wxStringTokenizer keywordTokenizer( GetKeywords(), wxS( " " ), wxTOKEN_STRTOK ); + + while( keywordTokenizer.HasMoreTokens() ) + terms.emplace_back( SEARCH_TERM( keywordTokenizer.GetNextToken(), 4 ) ); + + // Also include keywords as one long string, just in case + terms.emplace_back( SEARCH_TERM( GetKeywords(), 1 ) ); + terms.emplace_back( SEARCH_TERM( GetDescription(), 1 ) ); + + return terms; +} + + bool FOOTPRINT_INFO::InLibrary( const wxString& aLibrary ) const { return aLibrary == m_nickname; diff --git a/common/lib_tree_model.cpp b/common/lib_tree_model.cpp index d3d90e1016..16822c1bea 100644 --- a/common/lib_tree_model.cpp +++ b/common/lib_tree_model.cpp @@ -34,20 +34,6 @@ 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 LIB_TREE_NODE::ResetScore() { for( std::unique_ptr& child: m_Children ) @@ -85,20 +71,21 @@ void LIB_TREE_NODE::AssignIntrinsicRanks( bool presorted ) } -void LIB_TREE_NODE::SortNodes() +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 ); - } ); + [&]( std::unique_ptr& a, std::unique_ptr& b ) + { + return Compare( *a, *b, aUseScores ); + } ); for( std::unique_ptr& node: m_Children ) - node->SortNodes(); + node->SortNodes( aUseScores ); } -bool LIB_TREE_NODE::Compare( LIB_TREE_NODE const& aNode1, LIB_TREE_NODE const& aNode2 ) +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; @@ -126,6 +113,9 @@ bool LIB_TREE_NODE::Compare( LIB_TREE_NODE const& aNode1, LIB_TREE_NODE const& a 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; @@ -139,7 +129,6 @@ LIB_TREE_NODE::LIB_TREE_NODE() m_IntrinsicRank( 0 ), m_Score( kLowestDefaultScore ), m_Pinned( false ), - m_Normalized( false ), m_Unit( 0 ), m_IsRoot( false ) {} @@ -167,15 +156,9 @@ LIB_TREE_NODE_UNIT::LIB_TREE_NODE_UNIT( LIB_TREE_NODE* aParent, LIB_TREE_ITEM* a m_Name = namePrefix + " " + aItem->GetUnitReference( aUnit ); if( aItem->HasUnitDisplayName( aUnit ) ) - { m_Desc = aItem->GetUnitDisplayName( aUnit ); - } else - { m_Desc = wxEmptyString; - } - - m_MatchName = wxEmptyString; m_IntrinsicRank = -aUnit; } @@ -195,9 +178,7 @@ LIB_TREE_NODE_LIB_ID::LIB_TREE_NODE_LIB_ID( LIB_TREE_NODE* aParent, LIB_TREE_ITE aItem->GetChooserFields( m_Fields ); - m_MatchName = aItem->GetName(); - m_SearchText = aItem->GetSearchText(); - m_Normalized = false; + m_SearchTerms = aItem->GetSearchTerms(); m_IsRoot = aItem->IsRoot(); @@ -224,12 +205,10 @@ void LIB_TREE_NODE_LIB_ID::Update( LIB_TREE_ITEM* aItem ) m_Name = aItem->GetName(); m_Desc = aItem->GetDescription(); - m_MatchName = aItem->GetName(); aItem->GetChooserFields( m_Fields ); - m_SearchText = aItem->GetSearchText(); - m_Normalized = false; + m_SearchTerms = aItem->GetSearchTerms(); m_IsRoot = aItem->IsRoot(); m_Children.clear(); @@ -244,59 +223,13 @@ void LIB_TREE_NODE_LIB_ID::UpdateScore( EDA_COMBINED_MATCHER& aMatcher, const wx if( m_Score <= 0 ) return; // Leaf nodes without scores are out of the game. - if( !m_Normalized ) - { - m_MatchName = UnescapeString( m_MatchName ).Lower(); - m_SearchText = m_SearchText.Lower(); - m_Normalized = true; - } - - if( !aLib.IsEmpty() && m_Parent->m_MatchName != aLib ) + if( !aLib.IsEmpty() && m_Parent->m_Name.Lower() != aLib ) { m_Score = 0; return; } - // 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() == m_MatchName ) - { - m_Score += 1000; // exact match. High score :) - } - else if( aMatcher.Find( m_MatchName, matchers_fired, found_pos ) ) - { - // Substring match. The earlier in the string the better. - m_Score += matchPosScore( found_pos, 20 ) + 20; - } - else if( aMatcher.Find( m_Parent->m_MatchName, matchers_fired, found_pos ) ) - { - m_Score += 19; // parent name matches. score += 19 - } - else if( aMatcher.Find( m_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). - m_Score += matchPosScore( found_pos, 17 ) + 1; - } - } - else - { - // No match. That's it for this item. - m_Score = 0; - } - - // More matchers = better match - m_Score += 2 * matchers_fired; + m_Score = aMatcher.ScoreTerms( m_SearchTerms ); } @@ -305,7 +238,6 @@ LIB_TREE_NODE_LIB::LIB_TREE_NODE_LIB( LIB_TREE_NODE* aParent, wxString const& aN { m_Type = LIB; m_Name = aName; - m_MatchName = aName.Lower(); m_Desc = aDesc; m_Parent = aParent; m_LibId.SetLibNickname( aName ); @@ -338,27 +270,7 @@ void LIB_TREE_NODE_LIB::UpdateScore( EDA_COMBINED_MATCHER& aMatcher, const wxStr { // No children; we are a leaf. - if( !aLib.IsEmpty() ) - { - m_Score = m_MatchName == aLib ? 1000 : 0; - return; - } - - int found_pos = EDA_PATTERN_NOT_FOUND; - int matchers_fired = 0; - - if( aMatcher.GetPattern() == m_MatchName ) - { - m_Score += 1000; // exact match. High score :) - } - else if( aMatcher.Find( m_MatchName, matchers_fired, found_pos ) ) - { - // Substring match. The earlier in the string the better. - m_Score += matchPosScore( found_pos, 20 ) + 20; - } - - // More matchers = better match - m_Score += 2 * matchers_fired; + m_Score = aMatcher.ScoreTerms( m_SearchTerms ); } } diff --git a/common/lib_tree_model_adapter.cpp b/common/lib_tree_model_adapter.cpp index d3eeca54e8..b624264d46 100644 --- a/common/lib_tree_model_adapter.cpp +++ b/common/lib_tree_model_adapter.cpp @@ -68,6 +68,7 @@ LIB_TREE_MODEL_ADAPTER::LIB_TREE_MODEL_ADAPTER( EDA_BASE_FRAME* aParent, const wxString& aPinnedKey ) : m_parent( aParent ), m_filter( SYM_FILTER_NONE ), + m_sort_mode( BEST_MATCH ), m_show_units( true ), m_preselect_unit( 0 ), m_freeze( 0 ), @@ -208,7 +209,7 @@ void LIB_TREE_MODEL_ADAPTER::UpdateSearchString( const wxString& aSearch, bool a m_tree.UpdateScore( matcher, lib ); } - m_tree.SortNodes(); + m_tree.SortNodes( m_sort_mode == BEST_MATCH ); AfterReset(); Thaw(); } @@ -273,7 +274,7 @@ void LIB_TREE_MODEL_ADAPTER::resortTree() Freeze(); BeforeReset(); - m_tree.SortNodes(); + m_tree.SortNodes( m_sort_mode == BEST_MATCH ); AfterReset(); Thaw(); diff --git a/common/widgets/lib_tree.cpp b/common/widgets/lib_tree.cpp index 290883a6be..0111d6ef78 100644 --- a/common/widgets/lib_tree.cpp +++ b/common/widgets/lib_tree.cpp @@ -2,7 +2,7 @@ * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2014 Henner Zeller - * Copyright (C) 2014-2022 KiCad Developers, see AUTHORS.txt for contributors. + * 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 @@ -23,8 +23,10 @@ */ #include +#include #include #include +#include #include #include #include @@ -46,7 +48,9 @@ LIB_TREE::LIB_TREE( wxWindow* aParent, const wxString& aRecentSearchesKey, LIB_T HTML_WINDOW* aDetails ) : wxPanel( aParent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS | wxTAB_TRAVERSAL | wxNO_BORDER ), - m_lib_table( aLibTable ), m_adapter( aAdapter ), m_query_ctrl( nullptr ), + m_adapter( aAdapter ), + m_query_ctrl( nullptr ), + m_sort_ctrl( nullptr ), m_details_ctrl( nullptr ), m_inTimerEvent( false ), m_recentSearchesKey( aRecentSearchesKey ), @@ -73,6 +77,36 @@ LIB_TREE::LIB_TREE( wxWindow* aParent, const wxString& aRecentSearchesKey, LIB_T search_sizer->Add( m_query_ctrl, 1, wxEXPAND, 5 ); + m_sort_ctrl = new STD_BITMAP_BUTTON( this, wxID_ANY, wxNullBitmap, wxDefaultPosition, + wxDefaultSize, wxBU_AUTODRAW|0 ); + m_sort_ctrl->SetBitmap( KiBitmap( BITMAPS::small_sort_desc ) ); + m_sort_ctrl->Bind( wxEVT_LEFT_DOWN, + [&]( wxMouseEvent& aEvent ) + { + wxMenu menu; + + menu.Append( 4201, _( "Sort by Best Match" ), wxEmptyString, wxITEM_CHECK ); + menu.Append( 4202, _( "Sort Alphabetically" ), wxEmptyString, wxITEM_CHECK ); + + if( m_adapter->GetSortMode() == LIB_TREE_MODEL_ADAPTER::BEST_MATCH ) + menu.Check( 4201, true ); + else + menu.Check( 4202, true ); + + if( m_sort_ctrl->GetPopupMenuSelectionFromUser( menu ) == 0 ) + { + m_adapter->SetSortMode( LIB_TREE_MODEL_ADAPTER::BEST_MATCH ); + Regenerate( true ); + } + else + { + m_adapter->SetSortMode( LIB_TREE_MODEL_ADAPTER::ALPHABETIC ); + Regenerate( true ); + } + } ); + + search_sizer->Add( m_sort_ctrl, 0, wxEXPAND|wxALL, 1 ); + sizer->Add( search_sizer, 0, wxEXPAND, 5 ); m_query_ctrl->Bind( wxEVT_TEXT, &LIB_TREE::onQueryText, this ); diff --git a/eeschema/dialogs/dialog_choose_symbol.cpp b/eeschema/dialogs/dialog_choose_symbol.cpp index e0da67c29a..0c2d46cd39 100644 --- a/eeschema/dialogs/dialog_choose_symbol.cpp +++ b/eeschema/dialogs/dialog_choose_symbol.cpp @@ -2,7 +2,7 @@ * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2014 Henner Zeller - * Copyright (C) 2016-2022 KiCad Developers, see AUTHORS.txt for contributors. + * Copyright (C) 2016-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 @@ -203,6 +203,8 @@ DIALOG_CHOOSE_SYMBOL::DIALOG_CHOOSE_SYMBOL( SCH_BASE_FRAME* aParent, const wxStr wxSize dlgSize( panelCfg.width > 0 ? panelCfg.width : horizPixelsFromDU( 390 ), panelCfg.height > 0 ? panelCfg.height : vertPixelsFromDU( 300 ) ); SetSize( dlgSize ); + + aAdapter->SetSortMode( (LIB_TREE_MODEL_ADAPTER::SORT_MODE) cfg->m_SymChooserPanel.sort_mode ); } SetInitialFocus( m_tree->GetFocusTarget() ); @@ -280,6 +282,8 @@ DIALOG_CHOOSE_SYMBOL::~DIALOG_CHOOSE_SYMBOL() if( m_vsplitter ) cfg->m_SymChooserPanel.sash_pos_v = m_vsplitter->GetSashPosition(); + + cfg->m_SymChooserPanel.sort_mode = m_tree->GetSortMode(); } } diff --git a/eeschema/eeschema_settings.cpp b/eeschema/eeschema_settings.cpp index 5b7b25a32e..2038e8315d 100644 --- a/eeschema/eeschema_settings.cpp +++ b/eeschema/eeschema_settings.cpp @@ -446,6 +446,9 @@ EESCHEMA_SETTINGS::EESCHEMA_SETTINGS() : m_params.emplace_back( new PARAM( "symbol_chooser.height", &m_SymChooserPanel.height, -1 ) ); + m_params.emplace_back( new PARAM( "symbol_chooser.sort_mode", + &m_SymChooserPanel.sort_mode, 0 ) ); + m_params.emplace_back( new PARAM( "symbol_chooser.keep_symbol", &m_SymChooserPanel.keep_symbol, false ) ); diff --git a/eeschema/eeschema_settings.h b/eeschema/eeschema_settings.h index 22090fde31..74f5e3f9ea 100644 --- a/eeschema/eeschema_settings.h +++ b/eeschema/eeschema_settings.h @@ -234,6 +234,7 @@ public: int sash_pos_v; int width; int height; + int sort_mode; bool keep_symbol; bool place_all_units; }; diff --git a/eeschema/lib_symbol.cpp b/eeschema/lib_symbol.cpp index 461f24aafd..3d37e2ee0f 100644 --- a/eeschema/lib_symbol.cpp +++ b/eeschema/lib_symbol.cpp @@ -45,28 +45,34 @@ int LIB_SYMBOL::m_subpartIdSeparator = 0; int LIB_SYMBOL::m_subpartFirstId = 'A'; -wxString LIB_SYMBOL::GetSearchText() +std::vector LIB_SYMBOL::GetSearchTerms() { - // Matches are scored by offset from front of string, so inclusion of this spacer - // discounts matches found after it. - static const wxString discount( wxT( " " ) ); + std::vector terms; - wxString text = GetKeyWords() + discount + GetDescription(); - wxString footprint = GetFootprintField().GetText(); + terms.emplace_back( SEARCH_TERM( GetName(), 8 ) ); - if( !footprint.IsEmpty() ) - { - text += discount + footprint; - } + wxStringTokenizer keywordTokenizer( GetKeyWords(), wxS( " " ), wxTOKEN_STRTOK ); + + while( keywordTokenizer.HasMoreTokens() ) + terms.emplace_back( SEARCH_TERM( keywordTokenizer.GetNextToken(), 4 ) ); // TODO(JE) rework this later so we can highlight matches in their column std::map fields; GetChooserFields( fields ); - for( const auto& it : fields ) - text += discount + it.second; + for( const auto& [ name, text ] : fields ) + terms.emplace_back( SEARCH_TERM( text, 4 ) ); - return text; + // Also include keywords as one long string, just in case + terms.emplace_back( SEARCH_TERM( GetKeyWords(), 1 ) ); + terms.emplace_back( SEARCH_TERM( GetDescription(), 1 ) ); + + wxString footprint = GetFootprintField().GetText(); + + if( !footprint.IsEmpty() ) + terms.emplace_back( SEARCH_TERM( GetFootprintField().GetText(), 1 ) ); + + return terms; } @@ -77,7 +83,7 @@ void LIB_SYMBOL::GetChooserFields( std::map& aColumnMap ) LIB_FIELD* field = static_cast( &item ); if( field->ShowInChooser() ) - aColumnMap[field->GetName()] = field->EDA_TEXT::GetShownText(); + aColumnMap[field->GetName()] = field->EDA_TEXT::GetShownText( false ); } } diff --git a/eeschema/lib_symbol.h b/eeschema/lib_symbol.h index 4a6691c96d..6c66025896 100644 --- a/eeschema/lib_symbol.h +++ b/eeschema/lib_symbol.h @@ -177,7 +177,7 @@ public: return m_keyWords; } - wxString GetSearchText() override; + std::vector GetSearchTerms() override; wxString GetFootprint() override { diff --git a/eeschema/sch_plugins/legacy/sch_legacy_plugin.cpp b/eeschema/sch_plugins/legacy/sch_legacy_plugin.cpp index 5bfff2e0de..44ad9ceb64 100644 --- a/eeschema/sch_plugins/legacy/sch_legacy_plugin.cpp +++ b/eeschema/sch_plugins/legacy/sch_legacy_plugin.cpp @@ -152,7 +152,7 @@ SCH_SHEET* SCH_LEGACY_PLUGIN::Load( const wxString& aFileName, SCHEMATIC* aSchem if( m_path.IsEmpty() ) m_path = aSchematic->Prj().GetProjectPath(); - wxLogTrace( traceSchLegacyPlugin, "m_Normalized append path \"%s\".", m_path ); + wxLogTrace( traceSchLegacyPlugin, "Normalized append path \"%s\".", m_path ); } else { diff --git a/eeschema/symbol_editor/symbol_edit_frame.cpp b/eeschema/symbol_editor/symbol_edit_frame.cpp index 8d257bb46b..0d59e4a9fa 100644 --- a/eeschema/symbol_editor/symbol_edit_frame.cpp +++ b/eeschema/symbol_editor/symbol_edit_frame.cpp @@ -142,6 +142,7 @@ SYMBOL_EDIT_FRAME::SYMBOL_EDIT_FRAME( KIWAY* aKiway, wxWindow* aParent ) : SyncLibraries( false, loadingCancelled ); m_treePane = new SYMBOL_TREE_PANE( this, m_libMgr ); + m_treePane->GetLibTree()->SetSortMode( (LIB_TREE_MODEL_ADAPTER::SORT_MODE) m_settings->m_LibrarySortMode ); resolveCanvasType(); SwitchCanvas( m_canvasType ); @@ -304,6 +305,8 @@ void SYMBOL_EDIT_FRAME::SaveSettings( APP_SETTINGS_BASE* aCfg ) m_settings->m_ShowPinElectricalType = GetRenderSettings()->m_ShowPinsElectricalType; m_settings->m_LibWidth = m_treePane->GetSize().x; + + m_settings->m_LibrarySortMode = m_treePane->GetLibTree()->GetSortMode(); } diff --git a/eeschema/symbol_editor/symbol_editor_settings.cpp b/eeschema/symbol_editor/symbol_editor_settings.cpp index 87a71029e0..d61d6df1d5 100644 --- a/eeschema/symbol_editor/symbol_editor_settings.cpp +++ b/eeschema/symbol_editor/symbol_editor_settings.cpp @@ -1,7 +1,7 @@ /* * This program source code file is part of KiCad, a free EDA CAD application. * -* Copyright (C) 2020-2021 KiCad Developers, see AUTHORS.txt for contributors. +* Copyright (C) 2020-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 @@ -73,6 +73,9 @@ SYMBOL_EDITOR_SETTINGS::SYMBOL_EDITOR_SETTINGS() : m_params.emplace_back( new PARAM( "lib_table_width", &m_LibWidth, 250 ) ); + m_params.emplace_back( new PARAM( "library.sort_mode", + &m_LibrarySortMode, 0 ) ); + m_params.emplace_back( new PARAM( "edit_symbol_visible_columns", &m_EditSymbolVisibleColumns, "0 1 2 3 4 5 6 7" ) ); diff --git a/eeschema/symbol_editor/symbol_editor_settings.h b/eeschema/symbol_editor/symbol_editor_settings.h index ded7851639..0e3e4d91a9 100644 --- a/eeschema/symbol_editor/symbol_editor_settings.h +++ b/eeschema/symbol_editor/symbol_editor_settings.h @@ -1,7 +1,7 @@ /* * This program source code file is part of KiCad, a free EDA CAD application. * -* Copyright (C) 2020-2021 KiCad Developers, see AUTHORS.txt for contributors. +* Copyright (C) 2020-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 @@ -60,6 +60,8 @@ public: int m_LibWidth; + int m_LibrarySortMode; + wxString m_EditSymbolVisibleColumns; wxString m_PinTableVisibleColumns; diff --git a/eeschema/symbol_viewer_frame.cpp b/eeschema/symbol_viewer_frame.cpp index 8bbafab271..cec4f2cd98 100644 --- a/eeschema/symbol_viewer_frame.cpp +++ b/eeschema/symbol_viewer_frame.cpp @@ -795,16 +795,16 @@ bool SYMBOL_VIEWER_FRAME::ReCreateSymbolList() while( tokenizer.HasMoreTokens() ) { - const wxString term = tokenizer.GetNextToken().Lower(); - EDA_COMBINED_MATCHER matcher( term, CTX_LIBITEM ); + const wxString filterTerm = tokenizer.GetNextToken().Lower(); + EDA_COMBINED_MATCHER matcher( filterTerm, CTX_LIBITEM ); for( LIB_SYMBOL* symbol : symbols ) { - wxString search = symbol->GetName() + wxS( " " ) + symbol->GetSearchText(); - bool matched = matcher.Find( search.Lower() ); + std::vector searchTerms = symbol->GetSearchTerms(); + int matched = matcher.ScoreTerms( searchTerms ); - if( !matched && term.IsNumber() ) - matched = ( wxAtoi( term ) == (int)symbol->GetPinCount() ); + if( filterTerm.IsNumber() && wxAtoi( filterTerm ) == (int)symbol->GetPinCount() ) + matched++; if( !matched ) excludes.insert( symbol->GetName() ); diff --git a/include/eda_pattern_match.h b/include/eda_pattern_match.h index 5737a95150..53f53911e7 100644 --- a/include/eda_pattern_match.h +++ b/include/eda_pattern_match.h @@ -37,6 +37,26 @@ static const int EDA_PATTERN_NOT_FOUND = wxNOT_FOUND; +/* + * A structure for storing weighted search terms. + * + * NOTE: an exact match is scored at 8 * Score while a match at the start of the text is scored + * at 2 * Score. + */ +struct SEARCH_TERM +{ + SEARCH_TERM( const wxString& aText, int aScore ) : + Text( aText ), + Score( aScore ), + Normalized( false ) + {} + + wxString Text; + int Score; + bool Normalized; +}; + + /* * Interface for a pattern matcher, for which there are several implementations */ @@ -205,6 +225,8 @@ public: const wxString& GetPattern() const; + int ScoreTerms( std::vector& aWeightedTerms ); + private: // Add matcher if it can compile the pattern. void AddMatcher( const wxString& aPattern, std::unique_ptr aMatcher ); diff --git a/include/footprint_editor_settings.h b/include/footprint_editor_settings.h index 28b60a9398..b06ff90d1b 100644 --- a/include/footprint_editor_settings.h +++ b/include/footprint_editor_settings.h @@ -64,6 +64,8 @@ public: AUI_PANELS m_AuiPanels; + int m_LibrarySortMode; + USER_GRID m_UserGrid; bool m_PolarCoords; diff --git a/include/footprint_info.h b/include/footprint_info.h index d1a8ff0a05..ed55a5b20c 100644 --- a/include/footprint_info.h +++ b/include/footprint_info.h @@ -2,7 +2,7 @@ * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2011 Jean-Pierre Charras, - * Copyright (C) 1992-2021 KiCad Developers, see AUTHORS.txt for contributors. + * Copyright (C) 1992-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 @@ -89,14 +89,7 @@ public: return m_keywords; } - wxString GetSearchText() override - { - // Matches are scored by offset from front of string, so inclusion of this spacer - // discounts matches found after it. - static const wxString discount( wxT( " " ) ); - - return GetKeywords() + discount + GetDescription(); - } + std::vector GetSearchTerms() override; unsigned GetPadCount() { diff --git a/include/lib_tree_item.h b/include/lib_tree_item.h index 0015e0060b..00910135d2 100644 --- a/include/lib_tree_item.h +++ b/include/lib_tree_item.h @@ -1,7 +1,7 @@ /* * This program source code file is part of KiCad, a free EDA CAD application. * - * Copyright (C) 2018 KiCad Developers, see change_log.txt for contributors. + * Copyright (C) 2018-2023 KiCad Developers, see change_log.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 @@ -28,6 +28,7 @@ #include #include #include +#include /** * A mix-in to provide polymorphism between items stored in libraries (symbols, aliases @@ -55,7 +56,7 @@ public: */ virtual void GetChooserFields( std::map& aColumnMap ) {} - virtual wxString GetSearchText() { return wxEmptyString; } + virtual std::vector GetSearchTerms() { return std::vector(); } /** * For items having aliases, IsRoot() indicates the principal item. diff --git a/include/lib_tree_model.h b/include/lib_tree_model.h index f8c7e1639b..1b98568fc2 100644 --- a/include/lib_tree_model.h +++ b/include/lib_tree_model.h @@ -3,7 +3,7 @@ * * Copyright (C) 2017 Chris Pavlina * Copyright (C) 2014 Henner Zeller - * Copyright (C) 2014-2021 KiCad Developers, see AUTHORS.txt for contributors. + * 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 @@ -26,12 +26,10 @@ #include #include #include +#include #include -class EDA_COMBINED_MATCHER; - - /** * Model class in the component selector Model-View-Adapter (mediated MVC) * architecture. The other pieces are in: @@ -98,18 +96,24 @@ public: /** * Sort child nodes quickly and recursively (IntrinsicRanks must have been set). */ - void SortNodes(); + void SortNodes( bool aUseScores ); /** * Compare two nodes. Returns true if aNode1 < aNode2. */ - static bool Compare( LIB_TREE_NODE const& aNode1, LIB_TREE_NODE const& aNode2 ); + static bool Compare( LIB_TREE_NODE const& aNode1, LIB_TREE_NODE const& aNode2, + bool aUseScores ); LIB_TREE_NODE(); virtual ~LIB_TREE_NODE() {} - enum TYPE { - ROOT, LIB, LIBID, UNIT, INVALID + enum TYPE + { + ROOT, + LIB, + LIBID, + UNIT, + INVALID }; typedef std::vector> PTR_VECTOR; @@ -131,12 +135,9 @@ public: wxString m_Name; // Actual name of the part wxString m_Desc; // Description to be displayed wxString m_Footprint; // Footprint ID as a string (ie: the footprint field text) - wxString m_MatchName; // Normalized name for matching - wxString m_SearchText; // Descriptive text to search - bool m_Normalized; // Support for lazy normalization. - /// @see LIB_TREE_ITEMS::GetChooserFields - std::map m_Fields; + std::vector m_SearchTerms; /// List of weighted search terms + std::map m_Fields; /// @see LIB_TREE_ITEMS::GetChooserFields LIB_ID m_LibId; // LIB_ID determined by the parent library nickname and alias name. int m_Unit; // Actual unit, or zero diff --git a/include/lib_tree_model_adapter.h b/include/lib_tree_model_adapter.h index 16dac1568d..96f99babf3 100644 --- a/include/lib_tree_model_adapter.h +++ b/include/lib_tree_model_adapter.h @@ -3,7 +3,7 @@ * * Copyright (C) 2017 Chris Pavlina * Copyright (C) 2014 Henner Zeller - * Copyright (C) 2014-2021 KiCad Developers, see AUTHORS.txt for contributors. + * 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 @@ -133,6 +133,11 @@ public: NUM_COLS ///< The number of default tree columns }; + enum SORT_MODE + { + BEST_MATCH = 0, + ALPHABETIC + }; /** * Save the column widths to the config file. This requires the tree view to still be @@ -152,6 +157,9 @@ public: */ SYM_FILTER_TYPE GetFilter() const { return m_filter; } + void SetSortMode( SORT_MODE aMode ) { m_sort_mode = aMode; } + SORT_MODE GetSortMode() const { return m_sort_mode; } + /** * Whether or not to show units. May be set at any time; updates at the next * UpdateSearchString() @@ -415,6 +423,7 @@ private: EDA_BASE_FRAME* m_parent; SYM_FILTER_TYPE m_filter; + SORT_MODE m_sort_mode; bool m_show_units; LIB_ID m_preselect_lib_id; int m_preselect_unit; diff --git a/include/widgets/lib_tree.h b/include/widgets/lib_tree.h index 6282b884b1..08bfb614ee 100644 --- a/include/widgets/lib_tree.h +++ b/include/widgets/lib_tree.h @@ -2,7 +2,7 @@ * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2014 Henner Zeller - * Copyright (C) 2014-2022 KiCad Developers, see AUTHORS.txt for contributors. + * 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 @@ -35,6 +35,7 @@ class wxHtmlLinkEvent; class wxSearchCtrl; class wxTimer; class wxTimerEvent; +class STD_BITMAP_BUTTON; class ACTION_MENU; class LIB_ID; class LIB_TABLE; @@ -127,6 +128,12 @@ public: void SetSearchString( const wxString& aSearchString ); wxString GetSearchString() const; + /** + * Save/restore the sorting mode. + */ + void SetSortMode( LIB_TREE_MODEL_ADAPTER::SORT_MODE aMode ) { m_adapter->SetSortMode( aMode ); } + LIB_TREE_MODEL_ADAPTER::SORT_MODE GetSortMode() const { return m_adapter->GetSortMode(); } + /** * Regenerate the tree. */ @@ -214,20 +221,19 @@ protected: void onDebounceTimer( wxTimerEvent& aEvent ); protected: - LIB_TABLE* m_lib_table; - wxObjectDataPtr m_adapter; - wxSearchCtrl* m_query_ctrl; - WX_DATAVIEWCTRL* m_tree_ctrl; - HTML_WINDOW* m_details_ctrl; - wxTimer* m_debounceTimer; - bool m_inTimerEvent; + wxSearchCtrl* m_query_ctrl; + STD_BITMAP_BUTTON* m_sort_ctrl; + WX_DATAVIEWCTRL* m_tree_ctrl; + HTML_WINDOW* m_details_ctrl; + wxTimer* m_debounceTimer; + bool m_inTimerEvent; - LIB_ID m_last_libid; - wxString m_recentSearchesKey; + LIB_ID m_last_libid; + wxString m_recentSearchesKey; - bool m_skipNextRightClick; + bool m_skipNextRightClick; }; ///< Custom event sent when a new symbol is preselected diff --git a/pcbnew/dialogs/dialog_choose_footprint.cpp b/pcbnew/dialogs/dialog_choose_footprint.cpp index ae33dc35c4..60938c6201 100644 --- a/pcbnew/dialogs/dialog_choose_footprint.cpp +++ b/pcbnew/dialogs/dialog_choose_footprint.cpp @@ -119,26 +119,29 @@ DIALOG_CHOOSE_FOOTPRINT::DIALOG_CHOOSE_FOOTPRINT( PCB_BASE_FRAME* aParent, Layout(); - auto cfg = Pgm().GetSettingsManager().GetAppSettings(); + if( PCBNEW_SETTINGS* cfg = Pgm().GetSettingsManager().GetAppSettings() ) + { + // We specify the width of the right window (m_symbol_view_panel), because specify + // the width of the left window does not work as expected when SetSashGravity() is called + if( cfg->m_FootprintChooser.sash_h < 0 ) + cfg->m_FootprintChooser.sash_h = horizPixelsFromDU( 220 ); - // We specify the width of the right window (m_symbol_view_panel), because specify - // the width of the left window does not work as expected when SetSashGravity() is called - if( cfg->m_FootprintChooser.sash_h < 0 ) - cfg->m_FootprintChooser.sash_h = horizPixelsFromDU( 220 ); + m_hsplitter->SetSashPosition( cfg->m_FootprintChooser.sash_h ); - m_hsplitter->SetSashPosition( cfg->m_FootprintChooser.sash_h ); + if( cfg->m_FootprintChooser.sash_v < 0 ) + cfg->m_FootprintChooser.sash_v = horizPixelsFromDU( 230 ); - if( cfg->m_FootprintChooser.sash_v < 0 ) - cfg->m_FootprintChooser.sash_v = horizPixelsFromDU( 230 ); + if( m_vsplitter ) + m_vsplitter->SetSashPosition( cfg->m_FootprintChooser.sash_v ); - if( m_vsplitter ) - m_vsplitter->SetSashPosition( cfg->m_FootprintChooser.sash_v ); + int w = cfg->m_FootprintChooser.width < 0 ? + horizPixelsFromDU( 440 ) : cfg->m_FootprintChooser.width; + int h = cfg->m_FootprintChooser.height < 0 ? + horizPixelsFromDU( 340 ) : cfg->m_FootprintChooser.height; + SetSize( wxSize( w, h ) ); - int w = cfg->m_FootprintChooser.width < 0 ? - horizPixelsFromDU( 440 ) : cfg->m_FootprintChooser.width; - int h = cfg->m_FootprintChooser.height < 0 ? - horizPixelsFromDU( 340 ) : cfg->m_FootprintChooser.height; - SetSize( wxSize( w, h ) ); + aAdapter->SetSortMode( (LIB_TREE_MODEL_ADAPTER::SORT_MODE) cfg->m_FootprintChooser.sort_mode ); + } SetInitialFocus( m_tree->GetFocusTarget() ); } @@ -157,14 +160,17 @@ DIALOG_CHOOSE_FOOTPRINT::~DIALOG_CHOOSE_FOOTPRINT() m_dbl_click_timer->Stop(); delete m_dbl_click_timer; - auto cfg = Pgm().GetSettingsManager().GetAppSettings(); + if( PCBNEW_SETTINGS* cfg = Pgm().GetSettingsManager().GetAppSettings() ) + { + cfg->m_FootprintChooser.width = GetSize().x; + cfg->m_FootprintChooser.height = GetSize().y; + cfg->m_FootprintChooser.sash_h = m_hsplitter->GetSashPosition(); - cfg->m_FootprintChooser.width = GetSize().x; - cfg->m_FootprintChooser.height = GetSize().y; - cfg->m_FootprintChooser.sash_h = m_hsplitter->GetSashPosition(); + if( m_vsplitter ) + cfg->m_FootprintChooser.sash_v = m_vsplitter->GetSashPosition(); - if( m_vsplitter ) - cfg->m_FootprintChooser.sash_v = m_vsplitter->GetSashPosition(); + cfg->m_FootprintChooser.sort_mode = m_tree->GetSortMode(); + } } diff --git a/pcbnew/footprint_edit_frame.cpp b/pcbnew/footprint_edit_frame.cpp index a7a35f26e3..3f5063e204 100644 --- a/pcbnew/footprint_edit_frame.cpp +++ b/pcbnew/footprint_edit_frame.cpp @@ -626,6 +626,8 @@ void FOOTPRINT_EDIT_FRAME::LoadSettings( APP_SETTINGS_BASE* aCfg ) GetToolManager()->GetTool()->GetFilter() = cfg->m_SelectionFilter; m_selectionFilterPanel->SetCheckboxesFromFilter( cfg->m_SelectionFilter ); + + m_treePane->GetLibTree()->SetSortMode( (LIB_TREE_MODEL_ADAPTER::SORT_MODE) cfg->m_LibrarySortMode ); } } @@ -658,6 +660,8 @@ void FOOTPRINT_EDIT_FRAME::SaveSettings( APP_SETTINGS_BASE* aCfg ) cfg->m_AuiPanels.properties_splitter_proportion = m_propertiesPanel->SplitterProportion(); + + cfg->m_LibrarySortMode = m_treePane->GetLibTree()->GetSortMode(); } } diff --git a/pcbnew/footprint_editor_settings.cpp b/pcbnew/footprint_editor_settings.cpp index e2529d9b7c..7953ddc0a5 100644 --- a/pcbnew/footprint_editor_settings.cpp +++ b/pcbnew/footprint_editor_settings.cpp @@ -80,6 +80,9 @@ FOOTPRINT_EDITOR_SETTINGS::FOOTPRINT_EDITOR_SETTINGS() : m_params.emplace_back( new PARAM( "aui.show_properties", &m_AuiPanels.show_properties, false ) ); + m_params.emplace_back( new PARAM( "library.sort_mode", + &m_LibrarySortMode, 0 ) ); + m_params.emplace_back( new PARAM( "system.last_import_export_path", &m_LastImportExportPath, "" ) ); diff --git a/pcbnew/footprint_viewer_frame.cpp b/pcbnew/footprint_viewer_frame.cpp index e2836c5be8..a29428dd13 100644 --- a/pcbnew/footprint_viewer_frame.cpp +++ b/pcbnew/footprint_viewer_frame.cpp @@ -567,16 +567,16 @@ void FOOTPRINT_VIEWER_FRAME::ReCreateFootprintList() while( tokenizer.HasMoreTokens() ) { - const wxString term = tokenizer.GetNextToken().Lower(); - EDA_COMBINED_MATCHER matcher( term, CTX_LIBITEM ); + const wxString filterTerm = tokenizer.GetNextToken().Lower(); + EDA_COMBINED_MATCHER matcher( filterTerm, CTX_LIBITEM ); for( const std::unique_ptr& footprint : fp_info_list->GetList() ) { - wxString search = footprint->GetFootprintName() + wxS( " " ) + footprint->GetSearchText(); - bool matched = matcher.Find( search.Lower() ); + std::vector searchTerms = footprint->GetSearchTerms(); + int matched = matcher.ScoreTerms( searchTerms ); - if( !matched && term.IsNumber() ) - matched = ( wxAtoi( term ) == (int)footprint->GetPadCount() ); + if( filterTerm.IsNumber() && wxAtoi( filterTerm ) == (int)footprint->GetPadCount() ) + matched++; if( !matched ) excludes.insert( footprint->GetFootprintName() ); diff --git a/pcbnew/pcbnew_settings.cpp b/pcbnew/pcbnew_settings.cpp index 1ae99836a4..c770e67f6f 100644 --- a/pcbnew/pcbnew_settings.cpp +++ b/pcbnew/pcbnew_settings.cpp @@ -1,7 +1,7 @@ /* * This program source code file is part of KiCad, a free EDA CAD application. * -* Copyright (C) 2020-2022 KiCad Developers, see AUTHORS.txt for contributors. +* Copyright (C) 2020-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 @@ -130,6 +130,9 @@ PCBNEW_SETTINGS::PCBNEW_SETTINGS() m_params.emplace_back( new PARAM( "footprint_chooser.sash_v", &m_FootprintChooser.sash_v, -1 ) ); + m_params.emplace_back( new PARAM( "footprint_chooser.sort_mode", + &m_FootprintChooser.sort_mode, 0 ) ); + m_params.emplace_back( new PARAM( "editing.flip_left_right", &m_FlipLeftRight, true ) ); diff --git a/pcbnew/pcbnew_settings.h b/pcbnew/pcbnew_settings.h index 3ebb4ca6ef..70fb57c5d1 100644 --- a/pcbnew/pcbnew_settings.h +++ b/pcbnew/pcbnew_settings.h @@ -2,7 +2,7 @@ * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2020 Jon Evans - * Copyright (C) 2020-2022 KiCad Developers, see AUTHORS.txt for contributors. + * Copyright (C) 2020-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 @@ -296,6 +296,7 @@ public: int height; int sash_h; int sash_v; + int sort_mode; }; struct ZONES