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.

361 lines
11 KiB

2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
  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) 2023 CERN
  7. * Copyright (C) 2014-2023 KiCad Developers, see AUTHORS.txt for contributors.
  8. *
  9. * This program is free software: you can redistribute it and/or modify it
  10. * under the terms of the GNU General Public License as published by the
  11. * Free Software Foundation, either version 3 of the License, or (at your
  12. * option) any later version.
  13. *
  14. * This program is distributed in the hope that it will be useful, but
  15. * WITHOUT ANY WARRANTY; without even the implied warranty of
  16. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  17. * General Public License for more details.
  18. *
  19. * You should have received a copy of the GNU General Public License along
  20. * with this program. If not, see <http://www.gnu.org/licenses/>.
  21. */
  22. #include <lib_tree_model.h>
  23. #include <algorithm>
  24. #include <eda_pattern_match.h>
  25. #include <lib_tree_item.h>
  26. #include <pgm_base.h>
  27. #include <string_utils.h>
  28. void LIB_TREE_NODE::ResetScore()
  29. {
  30. for( std::unique_ptr<LIB_TREE_NODE>& child: m_Children )
  31. child->ResetScore();
  32. m_Score = 0;
  33. }
  34. void LIB_TREE_NODE::AssignIntrinsicRanks( bool presorted )
  35. {
  36. std::vector<LIB_TREE_NODE*> sort_buf;
  37. if( presorted )
  38. {
  39. int max = m_Children.size() - 1;
  40. for( int i = 0; i <= max; ++i )
  41. m_Children[i]->m_IntrinsicRank = max - i;
  42. }
  43. else
  44. {
  45. for( std::unique_ptr<LIB_TREE_NODE>& child: m_Children )
  46. sort_buf.push_back( child.get() );
  47. std::sort( sort_buf.begin(), sort_buf.end(),
  48. []( LIB_TREE_NODE* a, LIB_TREE_NODE* b ) -> bool
  49. {
  50. return StrNumCmp( a->m_Name, b->m_Name, true ) > 0;
  51. } );
  52. for( int i = 0; i < (int) sort_buf.size(); ++i )
  53. sort_buf[i]->m_IntrinsicRank = i;
  54. }
  55. }
  56. void LIB_TREE_NODE::SortNodes( bool aUseScores )
  57. {
  58. std::sort( m_Children.begin(), m_Children.end(),
  59. [&]( std::unique_ptr<LIB_TREE_NODE>& a, std::unique_ptr<LIB_TREE_NODE>& b )
  60. {
  61. return Compare( *a, *b, aUseScores );
  62. } );
  63. for( std::unique_ptr<LIB_TREE_NODE>& node: m_Children )
  64. node->SortNodes( aUseScores );
  65. }
  66. bool LIB_TREE_NODE::Compare( LIB_TREE_NODE const& aNode1, LIB_TREE_NODE const& aNode2,
  67. bool aUseScores )
  68. {
  69. if( aNode1.m_Type != aNode2.m_Type )
  70. return aNode1.m_Type < aNode2.m_Type;
  71. // Recently used sorts at top
  72. if( aNode1.m_Name.StartsWith( wxT( "-- " ) ) )
  73. {
  74. if( aNode2.m_Name.StartsWith( wxT( "-- " ) ) )
  75. {
  76. // Make sure -- Recently Used is always at the top
  77. if( aNode1.m_Name.StartsWith( wxT( "-- Recently Used" ) ) )
  78. return true;
  79. else if( aNode2.m_Name.StartsWith( wxT( "-- Recently Used" ) ) )
  80. return false;
  81. return aNode1.m_IntrinsicRank > aNode2.m_IntrinsicRank;
  82. }
  83. else
  84. {
  85. return true;
  86. }
  87. }
  88. else if( aNode2.m_Name.StartsWith( wxT( "-- " ) ) )
  89. {
  90. return false;
  91. }
  92. // Pinned nodes go next
  93. if( aNode1.m_Pinned && !aNode2.m_Pinned )
  94. return true;
  95. else if( aNode2.m_Pinned && !aNode1.m_Pinned )
  96. return false;
  97. if( aUseScores && aNode1.m_Score != aNode2.m_Score )
  98. return aNode1.m_Score > aNode2.m_Score;
  99. if( aNode1.m_IntrinsicRank != aNode2.m_IntrinsicRank )
  100. return aNode1.m_IntrinsicRank > aNode2.m_IntrinsicRank;
  101. return reinterpret_cast<const void*>( &aNode1 ) < reinterpret_cast<const void*>( &aNode2 );
  102. }
  103. LIB_TREE_NODE::LIB_TREE_NODE()
  104. : m_Parent( nullptr ),
  105. m_Type( INVALID ),
  106. m_IntrinsicRank( 0 ),
  107. m_Score( 0 ),
  108. m_Pinned( false ),
  109. m_PinCount( 0 ),
  110. m_Unit( 0 ),
  111. m_IsRoot( false )
  112. {}
  113. LIB_TREE_NODE_UNIT::LIB_TREE_NODE_UNIT( LIB_TREE_NODE* aParent, LIB_TREE_ITEM* aItem, int aUnit )
  114. {
  115. static void* locale = nullptr;
  116. static wxString namePrefix;
  117. // Fetching translations can take a surprising amount of time when loading libraries,
  118. // so only do it when necessary.
  119. if( Pgm().GetLocale() != locale )
  120. {
  121. namePrefix = _( "Unit" );
  122. locale = Pgm().GetLocale();
  123. }
  124. m_Parent = aParent;
  125. m_Type = UNIT;
  126. m_Unit = aUnit;
  127. m_LibId = aParent->m_LibId;
  128. m_Name = namePrefix + " " + aItem->GetUnitReference( aUnit );
  129. if( aItem->HasUnitDisplayName( aUnit ) )
  130. m_Desc = aItem->GetUnitDisplayName( aUnit );
  131. else
  132. m_Desc = wxEmptyString;
  133. m_IntrinsicRank = -aUnit;
  134. }
  135. void LIB_TREE_NODE_UNIT::UpdateScore( EDA_COMBINED_MATCHER* aMatcher, const wxString& aLib,
  136. std::function<bool( LIB_TREE_NODE& aNode )>* aFilter )
  137. {
  138. // aMatcher test results are inherited from parent
  139. if( aMatcher )
  140. m_Score = m_Parent->m_Score;
  141. // aFilter test is subtractive
  142. if( aFilter && !(*aFilter)(*this) )
  143. m_Score = 0;
  144. // show all nodes if no search/filter/etc. criteria are given
  145. if( !aMatcher && aLib.IsEmpty() && ( !aFilter || (*aFilter)(*this) ) )
  146. m_Score = 1;
  147. }
  148. LIB_TREE_NODE_ITEM::LIB_TREE_NODE_ITEM( LIB_TREE_NODE* aParent, LIB_TREE_ITEM* aItem )
  149. {
  150. m_Type = ITEM;
  151. m_Parent = aParent;
  152. m_LibId.SetLibNickname( aItem->GetLibNickname() );
  153. m_LibId.SetLibItemName( aItem->GetName() );
  154. m_Name = aItem->GetName();
  155. m_Desc = aItem->GetDesc();
  156. m_Footprint = aItem->GetFootprint();
  157. m_PinCount = aItem->GetPinCount();
  158. aItem->GetChooserFields( m_Fields );
  159. m_SearchTerms = aItem->GetSearchTerms();
  160. m_IsRoot = aItem->IsRoot();
  161. if( aItem->GetSubUnitCount() > 1 )
  162. {
  163. for( int u = 1; u <= aItem->GetSubUnitCount(); ++u )
  164. AddUnit( aItem, u );
  165. }
  166. }
  167. LIB_TREE_NODE_UNIT& LIB_TREE_NODE_ITEM::AddUnit( LIB_TREE_ITEM* aItem, int aUnit )
  168. {
  169. LIB_TREE_NODE_UNIT* unit = new LIB_TREE_NODE_UNIT( this, aItem, aUnit );
  170. m_Children.push_back( std::unique_ptr<LIB_TREE_NODE>( unit ) );
  171. return *unit;
  172. }
  173. void LIB_TREE_NODE_ITEM::Update( LIB_TREE_ITEM* aItem )
  174. {
  175. m_LibId.SetLibNickname( aItem->GetLIB_ID().GetLibNickname() );
  176. m_LibId.SetLibItemName( aItem->GetName() );
  177. m_Name = aItem->GetName();
  178. m_Desc = aItem->GetDesc();
  179. aItem->GetChooserFields( m_Fields );
  180. m_SearchTerms = aItem->GetSearchTerms();
  181. m_IsRoot = aItem->IsRoot();
  182. m_Children.clear();
  183. for( int u = 1; u <= aItem->GetSubUnitCount(); ++u )
  184. AddUnit( aItem, u );
  185. }
  186. void LIB_TREE_NODE_ITEM::UpdateScore( EDA_COMBINED_MATCHER* aMatcher, const wxString& aLib,
  187. std::function<bool( LIB_TREE_NODE& aNode )>* aFilter )
  188. {
  189. // aMatcher test is additive, but if we don't match the given term at all, it nulls out
  190. if( aMatcher )
  191. {
  192. int currentScore = aMatcher->ScoreTerms( m_SearchTerms );
  193. // This is a hack: the second phase of search in the adapter will look for a tokenized
  194. // LIB_ID and send the lib part down here. While we generally want to prune ourselves
  195. // out here (by setting score to -1) the first time we fail to match a search term,
  196. // we want to give the same search term a second chance if it has been split from a library
  197. // name.
  198. if( ( m_Score >= 0 || !aLib.IsEmpty() ) && currentScore > 0 )
  199. m_Score += currentScore;
  200. else
  201. m_Score = -1; // Item has failed to match this term, rule it out
  202. }
  203. // aFilter test is subtractive
  204. if( aFilter && !(*aFilter)(*this) )
  205. m_Score = 0;
  206. // show all nodes if no search/filter/etc. criteria are given
  207. if( !aMatcher && aLib.IsEmpty() && ( !aFilter || (*aFilter)(*this) ) )
  208. m_Score = 1;
  209. for( std::unique_ptr<LIB_TREE_NODE>& child: m_Children )
  210. child->UpdateScore( aMatcher, aLib, aFilter );
  211. }
  212. LIB_TREE_NODE_LIBRARY::LIB_TREE_NODE_LIBRARY( LIB_TREE_NODE* aParent, wxString const& aName,
  213. wxString const& aDesc )
  214. {
  215. m_Type = LIBRARY;
  216. m_Name = aName;
  217. m_Desc = aDesc;
  218. m_Parent = aParent;
  219. m_LibId.SetLibNickname( aName );
  220. m_SearchTerms.emplace_back( SEARCH_TERM( aName, 8 ) );
  221. }
  222. LIB_TREE_NODE_ITEM& LIB_TREE_NODE_LIBRARY::AddItem( LIB_TREE_ITEM* aItem )
  223. {
  224. LIB_TREE_NODE_ITEM* item = new LIB_TREE_NODE_ITEM( this, aItem );
  225. m_Children.push_back( std::unique_ptr<LIB_TREE_NODE>( item ) );
  226. return *item;
  227. }
  228. void LIB_TREE_NODE_LIBRARY::UpdateScore( EDA_COMBINED_MATCHER* aMatcher, const wxString& aLib,
  229. std::function<bool( LIB_TREE_NODE& aNode )>* aFilter )
  230. {
  231. int maxChildScore = 0;
  232. for( std::unique_ptr<LIB_TREE_NODE>& child: m_Children )
  233. {
  234. child->UpdateScore( aMatcher, aLib, aFilter );
  235. maxChildScore = std::max( maxChildScore, child->m_Score );
  236. }
  237. // Each time UpdateScore is called for a library, child (item) scores may go up or down.
  238. // If the all go down to zero, we need to make sure to drop the library from the list.
  239. if( maxChildScore > 0 )
  240. m_Score = std::max( m_Score, maxChildScore );
  241. else
  242. m_Score = 0;
  243. // aLib test is additive, but only when we've already accumulated some score from children
  244. if( !aLib.IsEmpty()
  245. && m_Name.Lower().Matches( aLib )
  246. && ( m_Score > 0 || m_Children.empty() ) )
  247. {
  248. m_Score += 1;
  249. }
  250. // aMatcher test is additive
  251. if( aMatcher )
  252. {
  253. int ownScore = aMatcher->ScoreTerms( m_SearchTerms );
  254. m_Score += ownScore;
  255. // If we have a hit on a library, show all children in that library
  256. if( maxChildScore <= 0 && ownScore > 0 )
  257. {
  258. for( std::unique_ptr<LIB_TREE_NODE>& child: m_Children )
  259. child->ForceScore( 1 );
  260. }
  261. }
  262. // show all nodes if no search/filter/etc. criteria are given
  263. if( m_Children.empty() && !aMatcher && aLib.IsEmpty() && ( !aFilter || (*aFilter)(*this) ) )
  264. m_Score = 1;
  265. }
  266. LIB_TREE_NODE_ROOT::LIB_TREE_NODE_ROOT()
  267. {
  268. m_Type = ROOT;
  269. }
  270. LIB_TREE_NODE_LIBRARY& LIB_TREE_NODE_ROOT::AddLib( wxString const& aName, wxString const& aDesc )
  271. {
  272. LIB_TREE_NODE_LIBRARY* lib = new LIB_TREE_NODE_LIBRARY( this, aName, aDesc );
  273. m_Children.push_back( std::unique_ptr<LIB_TREE_NODE>( lib ) );
  274. return *lib;
  275. }
  276. void LIB_TREE_NODE_ROOT::UpdateScore( EDA_COMBINED_MATCHER* aMatcher, const wxString& aLib,
  277. std::function<bool( LIB_TREE_NODE& aNode )>* aFilter )
  278. {
  279. for( std::unique_ptr<LIB_TREE_NODE>& child: m_Children )
  280. child->UpdateScore( aMatcher, aLib, aFilter );
  281. }