You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

339 lines
9.6 KiB

  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2017 Chris Pavlina <pavlina.chris@gmail.com>
  5. * Copyright (C) 2014 Henner Zeller <h.zeller@acm.org>
  6. * Copyright (C) 2014-2019 KiCad Developers, see AUTHORS.txt for contributors.
  7. *
  8. * This program is free software: you can redistribute it and/or modify it
  9. * under the terms of the GNU General Public License as published by the
  10. * Free Software Foundation, either version 3 of the License, or (at your
  11. * option) any later version.
  12. *
  13. * This program is distributed in the hope that it will be useful, but
  14. * WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  16. * General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU General Public License along
  19. * with this program. If not, see <http://www.gnu.org/licenses/>.
  20. */
  21. #include <lib_tree_model.h>
  22. #include <algorithm>
  23. #include <eda_pattern_match.h>
  24. #include <lib_tree_item.h>
  25. #include <utility>
  26. #include <pgm_base.h>
  27. #include <string_utils.h>
  28. // Each node gets this lowest score initially, without any matches applied.
  29. // Matches will then increase this score depending on match quality. This way,
  30. // an empty search string will result in all components being displayed as they
  31. // have the minimum score. However, in that case, we avoid expanding all the
  32. // nodes asd the result is very unspecific.
  33. static const unsigned kLowestDefaultScore = 1;
  34. // Creates a score depending on the position of a string match. If the position
  35. // is 0 (= prefix match), this returns the maximum score. This degrades until
  36. // pos == max, which returns a score of 0; Evertyhing else beyond that is just
  37. // 0. Only values >= 0 allowed for position and max.
  38. //
  39. // @param aPosition is the position a string has been found in a substring.
  40. // @param aMaximum is the maximum score this function returns.
  41. // @return position dependent score.
  42. static int matchPosScore(int aPosition, int aMaximum)
  43. {
  44. return ( aPosition < aMaximum ) ? aMaximum - aPosition : 0;
  45. }
  46. void LIB_TREE_NODE::ResetScore()
  47. {
  48. for( std::unique_ptr<LIB_TREE_NODE>& child: m_Children )
  49. child->ResetScore();
  50. m_Score = kLowestDefaultScore;
  51. }
  52. void LIB_TREE_NODE::AssignIntrinsicRanks( bool presorted )
  53. {
  54. std::vector<LIB_TREE_NODE*> sort_buf;
  55. if( presorted )
  56. {
  57. int max = m_Children.size() - 1;
  58. for( int i = 0; i <= max; ++i )
  59. m_Children[i]->m_IntrinsicRank = max - i;
  60. }
  61. else
  62. {
  63. for( std::unique_ptr<LIB_TREE_NODE>& child: m_Children )
  64. sort_buf.push_back( child.get() );
  65. std::sort( sort_buf.begin(), sort_buf.end(),
  66. []( LIB_TREE_NODE* a, LIB_TREE_NODE* b ) -> bool
  67. {
  68. return StrNumCmp( a->m_Name, b->m_Name, true ) > 0;
  69. } );
  70. for( int i = 0; i < (int) sort_buf.size(); ++i )
  71. sort_buf[i]->m_IntrinsicRank = i;
  72. }
  73. }
  74. void LIB_TREE_NODE::SortNodes()
  75. {
  76. std::sort( m_Children.begin(), m_Children.end(),
  77. []( std::unique_ptr<LIB_TREE_NODE>& a, std::unique_ptr<LIB_TREE_NODE>& b )
  78. {
  79. return Compare( *a, *b ) > 0;
  80. } );
  81. for( std::unique_ptr<LIB_TREE_NODE>& node: m_Children )
  82. node->SortNodes();
  83. }
  84. int LIB_TREE_NODE::Compare( LIB_TREE_NODE const& aNode1, LIB_TREE_NODE const& aNode2 )
  85. {
  86. if( aNode1.m_Type != aNode2.m_Type )
  87. return 0;
  88. if( aNode1.m_Score != aNode2.m_Score )
  89. return aNode1.m_Score - aNode2.m_Score;
  90. if( aNode1.m_Parent != aNode2.m_Parent )
  91. return 0;
  92. return aNode1.m_IntrinsicRank - aNode2.m_IntrinsicRank;
  93. }
  94. LIB_TREE_NODE::LIB_TREE_NODE()
  95. : m_Parent( nullptr ),
  96. m_Type( INVALID ),
  97. m_IntrinsicRank( 0 ),
  98. m_Score( kLowestDefaultScore ),
  99. m_Pinned( false ),
  100. m_Normalized( false ),
  101. m_Unit( 0 ),
  102. m_IsRoot( false )
  103. {}
  104. LIB_TREE_NODE_UNIT::LIB_TREE_NODE_UNIT( LIB_TREE_NODE* aParent, LIB_TREE_ITEM* aItem, int aUnit )
  105. {
  106. static void* locale = nullptr;
  107. static wxString namePrefix;
  108. // Fetching translations can take a surprising amount of time when loading libraries,
  109. // so only do it when necessary.
  110. if( Pgm().GetLocale() != locale )
  111. {
  112. namePrefix = _( "Unit" );
  113. locale = Pgm().GetLocale();
  114. }
  115. m_Parent = aParent;
  116. m_Type = UNIT;
  117. m_Unit = aUnit;
  118. m_LibId = aParent->m_LibId;
  119. m_Name = namePrefix + " " + aItem->GetUnitReference( aUnit );
  120. m_Desc = wxEmptyString;
  121. m_MatchName = wxEmptyString;
  122. m_IntrinsicRank = -aUnit;
  123. }
  124. LIB_TREE_NODE_LIB_ID::LIB_TREE_NODE_LIB_ID( LIB_TREE_NODE* aParent, LIB_TREE_ITEM* aItem )
  125. {
  126. m_Type = LIBID;
  127. m_Parent = aParent;
  128. m_LibId.SetLibNickname( aItem->GetLibNickname() );
  129. m_LibId.SetLibItemName( aItem->GetName() );
  130. m_Name = aItem->GetName();
  131. m_Desc = aItem->GetDescription();
  132. m_MatchName = aItem->GetName();
  133. m_SearchText = aItem->GetSearchText();
  134. m_Normalized = false;
  135. m_IsRoot = aItem->IsRoot();
  136. if( aItem->GetUnitCount() > 1 )
  137. {
  138. for( int u = 1; u <= aItem->GetUnitCount(); ++u )
  139. AddUnit( aItem, u );
  140. }
  141. }
  142. LIB_TREE_NODE_UNIT& LIB_TREE_NODE_LIB_ID::AddUnit( LIB_TREE_ITEM* aItem, int aUnit )
  143. {
  144. LIB_TREE_NODE_UNIT* unit = new LIB_TREE_NODE_UNIT( this, aItem, aUnit );
  145. m_Children.push_back( std::unique_ptr<LIB_TREE_NODE>( unit ) );
  146. return *unit;
  147. }
  148. void LIB_TREE_NODE_LIB_ID::Update( LIB_TREE_ITEM* aItem )
  149. {
  150. // Update is called when the names match, so just update the other fields.
  151. m_LibId.SetLibNickname( aItem->GetLibId().GetLibNickname() );
  152. m_Desc = aItem->GetDescription();
  153. m_SearchText = aItem->GetSearchText();
  154. m_Normalized = false;
  155. m_IsRoot = aItem->IsRoot();
  156. m_Children.clear();
  157. for( int u = 1; u <= aItem->GetUnitCount(); ++u )
  158. AddUnit( aItem, u );
  159. }
  160. void LIB_TREE_NODE_LIB_ID::UpdateScore( EDA_COMBINED_MATCHER& aMatcher )
  161. {
  162. if( m_Score <= 0 )
  163. return; // Leaf nodes without scores are out of the game.
  164. if( !m_Normalized )
  165. {
  166. m_MatchName = UnescapeString( m_MatchName ).Lower();
  167. m_SearchText = m_SearchText.Lower();
  168. m_Normalized = true;
  169. }
  170. // Keywords and description we only count if the match string is at
  171. // least two characters long. That avoids spurious, low quality
  172. // matches. Most abbreviations are at three characters long.
  173. int found_pos = EDA_PATTERN_NOT_FOUND;
  174. int matchers_fired = 0;
  175. if( aMatcher.GetPattern() == m_MatchName )
  176. {
  177. m_Score += 1000; // exact match. High score :)
  178. }
  179. else if( aMatcher.Find( m_MatchName, matchers_fired, found_pos ) )
  180. {
  181. // Substring match. The earlier in the string the better.
  182. m_Score += matchPosScore( found_pos, 20 ) + 20;
  183. }
  184. else if( aMatcher.Find( m_Parent->m_MatchName, matchers_fired, found_pos ) )
  185. {
  186. m_Score += 19; // parent name matches. score += 19
  187. }
  188. else if( aMatcher.Find( m_SearchText, matchers_fired, found_pos ) )
  189. {
  190. // If we have a very short search term (like one or two letters),
  191. // we don't want to accumulate scores if they just happen to be in
  192. // keywords or description as almost any one or two-letter
  193. // combination shows up in there.
  194. if( aMatcher.GetPattern().length() >= 2 )
  195. {
  196. // For longer terms, we add scores 1..18 for positional match
  197. // (higher in the front, where the keywords are).
  198. m_Score += matchPosScore( found_pos, 17 ) + 1;
  199. }
  200. }
  201. else
  202. {
  203. // No match. That's it for this item.
  204. m_Score = 0;
  205. }
  206. // More matchers = better match
  207. m_Score += 2 * matchers_fired;
  208. }
  209. LIB_TREE_NODE_LIB::LIB_TREE_NODE_LIB( LIB_TREE_NODE* aParent, wxString const& aName,
  210. wxString const& aDesc )
  211. {
  212. m_Type = LIB;
  213. m_Name = aName;
  214. m_MatchName = aName.Lower();
  215. m_Desc = aDesc;
  216. m_Parent = aParent;
  217. m_LibId.SetLibNickname( aName );
  218. }
  219. LIB_TREE_NODE_LIB_ID& LIB_TREE_NODE_LIB::AddItem( LIB_TREE_ITEM* aItem )
  220. {
  221. LIB_TREE_NODE_LIB_ID* item = new LIB_TREE_NODE_LIB_ID( this, aItem );
  222. m_Children.push_back( std::unique_ptr<LIB_TREE_NODE>( item ) );
  223. return *item;
  224. }
  225. void LIB_TREE_NODE_LIB::UpdateScore( EDA_COMBINED_MATCHER& aMatcher )
  226. {
  227. m_Score = 0;
  228. // We need to score leaf nodes, which are usually (but not always) children.
  229. if( m_Children.size() )
  230. {
  231. for( std::unique_ptr<LIB_TREE_NODE>& child: m_Children )
  232. {
  233. child->UpdateScore( aMatcher );
  234. m_Score = std::max( m_Score, child->m_Score );
  235. }
  236. }
  237. else
  238. {
  239. // No children; we are a leaf.
  240. int found_pos = EDA_PATTERN_NOT_FOUND;
  241. int matchers_fired = 0;
  242. if( aMatcher.GetPattern() == m_MatchName )
  243. {
  244. m_Score += 1000; // exact match. High score :)
  245. }
  246. else if( aMatcher.Find( m_MatchName, matchers_fired, found_pos ) )
  247. {
  248. // Substring match. The earlier in the string the better.
  249. m_Score += matchPosScore( found_pos, 20 ) + 20;
  250. }
  251. // More matchers = better match
  252. m_Score += 2 * matchers_fired;
  253. }
  254. }
  255. LIB_TREE_NODE_ROOT::LIB_TREE_NODE_ROOT()
  256. {
  257. m_Type = ROOT;
  258. }
  259. LIB_TREE_NODE_LIB& LIB_TREE_NODE_ROOT::AddLib( wxString const& aName, wxString const& aDesc )
  260. {
  261. LIB_TREE_NODE_LIB* lib = new LIB_TREE_NODE_LIB( this, aName, aDesc );
  262. m_Children.push_back( std::unique_ptr<LIB_TREE_NODE>( lib ) );
  263. return *lib;
  264. }
  265. void LIB_TREE_NODE_ROOT::UpdateScore( EDA_COMBINED_MATCHER& aMatcher )
  266. {
  267. for( std::unique_ptr<LIB_TREE_NODE>& child: m_Children )
  268. child->UpdateScore( aMatcher );
  269. }