From 769e18d37685550d65d2adadb38746a225cbe623 Mon Sep 17 00:00:00 2001 From: Chris Pavlina Date: Sun, 20 Dec 2015 15:52:39 -0500 Subject: [PATCH] Eeschema: add wildcard and regular expression search to componet select dialog. * Create base class EDA_PATTERN_MATCH to hand multiple pattern matching methods. * Create classes for regular expression and wildcard pattern matching. * Add new pattern matching semantics to component select dialog. * Thanks to Henner Zellar for improvments to the original patch. --- common/CMakeLists.txt | 1 + common/eda_pattern_match.cpp | 137 +++++++++++++++++++ eeschema/component_tree_search_container.cpp | 84 +++++++++++- include/eda_pattern_match.h | 98 +++++++++++++ 4 files changed, 316 insertions(+), 4 deletions(-) create mode 100644 common/eda_pattern_match.cpp create mode 100644 include/eda_pattern_match.h diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 27267186ef..45a5a24191 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -201,6 +201,7 @@ set( COMMON_SRCS dsnlexer.cpp eda_dde.cpp eda_doc.cpp + eda_pattern_match.cpp filter_reader.cpp # findkicadhelppath.cpp.notused deprecated, use searchhelpfilefullpath.cpp gestfich.cpp diff --git a/common/eda_pattern_match.cpp b/common/eda_pattern_match.cpp new file mode 100644 index 0000000000..cc4b8bf787 --- /dev/null +++ b/common/eda_pattern_match.cpp @@ -0,0 +1,137 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2015 Chris Pavlina + * Copyright (C) 2015 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 + * as published by the Free Software Foundation; either version 2 + * 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, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include +#include +#include + +bool EDA_PATTERN_MATCH_SUBSTR::SetPattern( const wxString& aPattern ) +{ + m_pattern = aPattern; + return true; +} + + +int EDA_PATTERN_MATCH_SUBSTR::Find( const wxString& aCandidate ) const +{ + int loc = aCandidate.Find( m_pattern ); + + return ( loc == wxNOT_FOUND ) ? EDA_PATTERN_NOT_FOUND : loc; +} + + +/** + * Context class to set wx loglevel for a block, and always restore it at the end. + */ +class WX_LOGLEVEL_CONTEXT +{ + wxLogLevel m_old_level; + +public: + WX_LOGLEVEL_CONTEXT( wxLogLevel level ) + { + m_old_level = wxLog::GetLogLevel(); + wxLog::SetLogLevel( level ); + } + + ~WX_LOGLEVEL_CONTEXT() + { + wxLog::SetLogLevel( m_old_level ); + } +}; + + +bool EDA_PATTERN_MATCH_REGEX::SetPattern( const wxString& aPattern ) +{ + m_pattern = aPattern; + + // Evil and undocumented: wxRegEx::Compile calls wxLogError on error, even + // though it promises to just return false. Silence the error. + WX_LOGLEVEL_CONTEXT ctx( wxLOG_FatalError ); + + return m_regex.Compile( aPattern, wxRE_ADVANCED ); +} + + +int EDA_PATTERN_MATCH_REGEX::Find( const wxString& aCandidate ) const +{ + if( m_regex.IsValid() ) + { + if( m_regex.Matches( aCandidate ) ) + { + size_t start, len; + m_regex.GetMatch( &start, &len, 0 ); + return ( start > INT_MAX ) ? INT_MAX : start; + } + else + { + return EDA_PATTERN_NOT_FOUND; + } + } + else + { + int loc = aCandidate.Find( m_pattern ); + return ( loc == wxNOT_FOUND ) ? EDA_PATTERN_NOT_FOUND : loc; + } +} + + +bool EDA_PATTERN_MATCH_WILDCARD::SetPattern( const wxString& aPattern ) +{ + // Compile the wildcard string to a regular expression + wxString regex; + regex.Alloc( 2 * aPattern.Length() ); // no need to keep resizing, we know the size roughly + + const wxString to_replace = wxT( ".*+?^${}()|[]/\\" ); + + for( wxString::const_iterator it = aPattern.begin(); it < aPattern.end(); ++it ) + { + wxUniChar c = *it; + if( c == '?' ) + { + regex += wxT( "." ); + } + else if( c == '*' ) + { + regex += wxT( ".*" ); + } + else if( to_replace.Find( c ) != wxNOT_FOUND ) + { + regex += "\\"; + regex += c; + } + else + { + regex += c; + } + } + + return EDA_PATTERN_MATCH_REGEX::SetPattern( regex ); +} + + +int EDA_PATTERN_MATCH_WILDCARD::Find( const wxString& aCandidate ) const +{ + return EDA_PATTERN_MATCH_REGEX::Find( aCandidate ); +} diff --git a/eeschema/component_tree_search_container.cpp b/eeschema/component_tree_search_container.cpp index c3061f95e9..633af5494c 100644 --- a/eeschema/component_tree_search_container.cpp +++ b/eeschema/component_tree_search_container.cpp @@ -35,6 +35,8 @@ #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 @@ -113,6 +115,7 @@ COMPONENT_TREE_SEARCH_CONTAINER::~COMPONENT_TREE_SEARCH_CONTAINER() { BOOST_FOREACH( TREE_NODE* node, m_nodes ) delete node; + m_nodes.clear(); } @@ -225,12 +228,15 @@ LIB_ALIAS* COMPONENT_TREE_SEARCH_CONTAINER::GetSelectedAlias( int* aUnit ) BOOST_FOREACH( TREE_NODE* node, m_nodes ) { - if( node->MatchScore > 0 && node->TreeId == select_id ) { + if( node->MatchScore > 0 && node->TreeId == select_id ) + { if( aUnit && node->Unit > 0 ) *aUnit = node->Unit; + return node->Alias; } } + return NULL; } @@ -249,6 +255,72 @@ static int matchPosScore(int aPosition, int aMaximum) } +namespace +{ +class EDA_COMBINED_MATCHER +{ +public: + EDA_COMBINED_MATCHER( const wxString &aPattern ) + { + // Whatever syntax users prefer, it shall be matched. + AddMatcher( aPattern, new EDA_PATTERN_MATCH_REGEX() ); + AddMatcher( aPattern, new EDA_PATTERN_MATCH_WILDCARD() ); + // If any of the above matchers couldn't be created because the pattern + // syntax does not match, the substring will try its best. + AddMatcher( aPattern, new EDA_PATTERN_MATCH_SUBSTR() ); + } + + ~EDA_COMBINED_MATCHER() + { + BOOST_FOREACH( const EDA_PATTERN_MATCH* matcher, m_matchers ) + delete matcher; + } + + /* + * Look in all existing matchers, return the earliest match of any of + * the existing. Returns EDA_PATTERN_NOT_FOUND if no luck. + */ + int Find( const wxString &aTerm, int *aMatchersTriggered ) + { + int result = EDA_PATTERN_NOT_FOUND; + + BOOST_FOREACH( const EDA_PATTERN_MATCH* matcher, m_matchers ) + { + int local_find = matcher->Find( aTerm ); + + if ( local_find != EDA_PATTERN_NOT_FOUND ) + { + *aMatchersTriggered += 1; + + if ( local_find < result ) + { + result = local_find; + } + } + } + + return result; + } + +private: + // Add matcher if it can compile the pattern. + void AddMatcher( const wxString &aPattern, EDA_PATTERN_MATCH *aMatcher ) + { + if ( aMatcher->SetPattern( aPattern ) ) + { + m_matchers.push_back( aMatcher ); + } + else + { + delete aMatcher; + } + } + + std::vector m_matchers; +}; +} + + void COMPONENT_TREE_SEARCH_CONTAINER::UpdateSearchTerm( const wxString& aSearch ) { if( m_tree == NULL ) @@ -286,6 +358,7 @@ void COMPONENT_TREE_SEARCH_CONTAINER::UpdateSearchTerm( const wxString& aSearch while ( tokenizer.HasMoreTokens() ) { const wxString term = tokenizer.GetNextToken().Lower(); + EDA_COMBINED_MATCHER matcher( term ); BOOST_FOREACH( TREE_NODE* node, m_nodes ) { @@ -299,17 +372,18 @@ void COMPONENT_TREE_SEARCH_CONTAINER::UpdateSearchTerm( const wxString& aSearch // least two characters long. That avoids spurious, low quality // matches. Most abbreviations are at three characters long. int found_pos; + int matcher_fired = 0; if( term == node->MatchName ) node->MatchScore += 1000; // exact match. High score :) - else if( (found_pos = node->MatchName.Find( term ) ) != wxNOT_FOUND ) + else if( (found_pos = matcher.Find( node->MatchName, &matcher_fired ) ) != EDA_PATTERN_NOT_FOUND ) { // Substring match. The earlier in the string the better. score += 20..40 node->MatchScore += matchPosScore( found_pos, 20 ) + 20; } - else if( node->Parent->MatchName.Find( term ) != wxNOT_FOUND ) + else if( matcher.Find( node->Parent->MatchName, &matcher_fired ) != EDA_PATTERN_NOT_FOUND ) node->MatchScore += 19; // parent name matches. score += 19 - else if( ( found_pos = node->SearchText.Find( term ) ) != wxNOT_FOUND ) + else if( ( found_pos = matcher.Find( node->SearchText, &matcher_fired ) ) != EDA_PATTERN_NOT_FOUND ) { // 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 @@ -322,6 +396,8 @@ void COMPONENT_TREE_SEARCH_CONTAINER::UpdateSearchTerm( const wxString& aSearch } else node->MatchScore = 0; // No match. That's it for this item. + + node->MatchScore += 2 * matcher_fired; } } diff --git a/include/eda_pattern_match.h b/include/eda_pattern_match.h new file mode 100644 index 0000000000..db0ec968a1 --- /dev/null +++ b/include/eda_pattern_match.h @@ -0,0 +1,98 @@ +/* -*- c++ -*- + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2015 Chris Pavlina + * Copyright (C) 2015 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 + * as published by the Free Software Foundation; either version 2 + * 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, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/** + * @file eda_pattern_match.h + * @brief Abstract pattern-matching tool and implementations. + */ + +#ifndef EDA_PATTERN_MATCH_H +#define EDA_PATTERN_MATCH_H + +#include +#include +#include +#include + +static const int EDA_PATTERN_NOT_FOUND = wxNOT_FOUND; + +/* + * Interface for a pattern matcher, for which there are several implementations + */ +class EDA_PATTERN_MATCH +{ +public: + virtual ~EDA_PATTERN_MATCH() {} + + /** + * Set the pattern against which candidates will be matched. If the pattern + * can not be processed, returns false. + */ + virtual bool SetPattern( const wxString& aPattern ) = 0; + + /** + * Return the location of a match iff a given candidate string matches the set pattern. + * Otherwise, return EDA_PATTERN_NOT_FOUND. + */ + virtual int Find( const wxString& aCandidate ) const = 0; +}; + + +/* + * Match simple substring + */ +class EDA_PATTERN_MATCH_SUBSTR : public EDA_PATTERN_MATCH +{ +public: + virtual bool SetPattern( const wxString& aPattern ); + virtual int Find( const wxString& aCandidate ) const; + +protected: + wxString m_pattern; +}; + + +/* + * Match regular expression + */ +class EDA_PATTERN_MATCH_REGEX : public EDA_PATTERN_MATCH +{ +public: + virtual bool SetPattern( const wxString& aPattern ); + virtual int Find( const wxString& aCandidate ) const; + +protected: + wxString m_pattern; + wxRegEx m_regex; +}; + + +class EDA_PATTERN_MATCH_WILDCARD : public EDA_PATTERN_MATCH_REGEX +{ +public: + virtual bool SetPattern( const wxString& aPattern ); + virtual int Find( const wxString& aCandidate ) const; +}; + +#endif // EDA_PATTERN_MATCH_H