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.

385 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
  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. // Start by checking the name of aNode2, because we
  78. // want to satisfy the irreflexive property of the
  79. // strict weak ordering.
  80. if( aNode2.m_Name.StartsWith( wxT( "-- Recently Used" ) ) )
  81. return false;
  82. else if( aNode1.m_Name.StartsWith( wxT( "-- Recently Used" ) ) )
  83. return true;
  84. return aNode1.m_IntrinsicRank > aNode2.m_IntrinsicRank;
  85. }
  86. else
  87. {
  88. return true;
  89. }
  90. }
  91. else if( aNode2.m_Name.StartsWith( wxT( "-- " ) ) )
  92. {
  93. return false;
  94. }
  95. // Pinned nodes go next
  96. if( aNode1.m_Pinned && !aNode2.m_Pinned )
  97. return true;
  98. else if( aNode2.m_Pinned && !aNode1.m_Pinned )
  99. return false;
  100. if( aUseScores && aNode1.m_Score != aNode2.m_Score )
  101. return aNode1.m_Score > aNode2.m_Score;
  102. if( aNode1.m_IntrinsicRank != aNode2.m_IntrinsicRank )
  103. return aNode1.m_IntrinsicRank > aNode2.m_IntrinsicRank;
  104. return reinterpret_cast<const void*>( &aNode1 ) < reinterpret_cast<const void*>( &aNode2 );
  105. }
  106. LIB_TREE_NODE::LIB_TREE_NODE()
  107. : m_Parent( nullptr ),
  108. m_Type( TYPE::INVALID ),
  109. m_IntrinsicRank( 0 ),
  110. m_Score( 0 ),
  111. m_Pinned( false ),
  112. m_PinCount( 0 ),
  113. m_Unit( 0 ),
  114. m_IsRoot( false )
  115. {}
  116. LIB_TREE_NODE_UNIT::LIB_TREE_NODE_UNIT( LIB_TREE_NODE* aParent, LIB_TREE_ITEM* aItem, int aUnit )
  117. {
  118. static void* locale = nullptr;
  119. static wxString namePrefix;
  120. // Fetching translations can take a surprising amount of time when loading libraries,
  121. // so only do it when necessary.
  122. if( Pgm().GetLocale() != locale )
  123. {
  124. namePrefix = _( "Unit" );
  125. locale = Pgm().GetLocale();
  126. }
  127. m_Parent = aParent;
  128. m_Type = TYPE::UNIT;
  129. m_Unit = aUnit;
  130. m_LibId = aParent->m_LibId;
  131. m_Name = namePrefix + " " + aItem->GetUnitReference( aUnit );
  132. if( aItem->HasUnitDisplayName( aUnit ) )
  133. m_Desc = aItem->GetUnitDisplayName( aUnit );
  134. else
  135. m_Desc = wxEmptyString;
  136. m_IntrinsicRank = -aUnit;
  137. }
  138. void LIB_TREE_NODE_UNIT::UpdateScore( EDA_COMBINED_MATCHER* aMatcher, const wxString& aLib,
  139. std::function<bool( LIB_TREE_NODE& aNode )>* aFilter )
  140. {
  141. // aMatcher test results are inherited from parent
  142. if( aMatcher )
  143. m_Score = m_Parent->m_Score;
  144. // aFilter test is subtractive
  145. if( aFilter && !(*aFilter)(*this) )
  146. m_Score = 0;
  147. // show all nodes if no search/filter/etc. criteria are given
  148. if( !aMatcher && aLib.IsEmpty() && ( !aFilter || (*aFilter)(*this) ) )
  149. m_Score = 1;
  150. }
  151. LIB_TREE_NODE_ITEM::LIB_TREE_NODE_ITEM( LIB_TREE_NODE* aParent, LIB_TREE_ITEM* aItem )
  152. {
  153. m_Type = TYPE::ITEM;
  154. m_Parent = aParent;
  155. m_LibId.SetLibNickname( aItem->GetLibNickname() );
  156. m_LibId.SetLibItemName( aItem->GetName() );
  157. m_Name = aItem->GetName();
  158. m_Desc = aItem->GetDesc();
  159. m_Footprint = aItem->GetFootprint();
  160. m_PinCount = aItem->GetPinCount();
  161. aItem->GetChooserFields( m_Fields );
  162. m_SearchTerms = aItem->GetSearchTerms();
  163. m_IsRoot = aItem->IsRoot();
  164. if( aItem->GetSubUnitCount() > 1 )
  165. {
  166. for( int u = 1; u <= aItem->GetSubUnitCount(); ++u )
  167. AddUnit( aItem, u );
  168. }
  169. }
  170. LIB_TREE_NODE_UNIT& LIB_TREE_NODE_ITEM::AddUnit( LIB_TREE_ITEM* aItem, int aUnit )
  171. {
  172. LIB_TREE_NODE_UNIT* unit = new LIB_TREE_NODE_UNIT( this, aItem, aUnit );
  173. m_Children.push_back( std::unique_ptr<LIB_TREE_NODE>( unit ) );
  174. return *unit;
  175. }
  176. void LIB_TREE_NODE_ITEM::Update( LIB_TREE_ITEM* aItem )
  177. {
  178. m_LibId.SetLibNickname( aItem->GetLIB_ID().GetLibNickname() );
  179. m_LibId.SetLibItemName( aItem->GetName() );
  180. m_Name = aItem->GetName();
  181. m_Desc = aItem->GetDesc();
  182. aItem->GetChooserFields( m_Fields );
  183. m_SearchTerms = aItem->GetSearchTerms();
  184. m_IsRoot = aItem->IsRoot();
  185. m_Children.clear();
  186. for( int u = 1; u <= aItem->GetSubUnitCount(); ++u )
  187. AddUnit( aItem, u );
  188. }
  189. void LIB_TREE_NODE_ITEM::UpdateScore( EDA_COMBINED_MATCHER* aMatcher, const wxString& aLib,
  190. std::function<bool( LIB_TREE_NODE& aNode )>* aFilter )
  191. {
  192. // aMatcher test is additive, but if we don't match the given term at all, it nulls out
  193. if( aMatcher )
  194. {
  195. int currentScore = aMatcher->ScoreTerms( m_SearchTerms );
  196. // This is a hack: the second phase of search in the adapter will look for a tokenized
  197. // LIB_ID and send the lib part down here. While we generally want to prune ourselves
  198. // out here (by setting score to -1) the first time we fail to match a search term,
  199. // we want to give the same search term a second chance if it has been split from a library
  200. // name.
  201. if( ( m_Score >= 0 || !aLib.IsEmpty() ) && currentScore > 0 )
  202. m_Score += currentScore;
  203. else
  204. m_Score = -1; // Item has failed to match this term, rule it out
  205. }
  206. // aFilter test is subtractive
  207. if( aFilter && !(*aFilter)(*this) )
  208. m_Score = 0;
  209. // show all nodes if no search/filter/etc. criteria are given
  210. if( !aMatcher && aLib.IsEmpty() && ( !aFilter || (*aFilter)(*this) ) )
  211. m_Score = 1;
  212. for( std::unique_ptr<LIB_TREE_NODE>& child: m_Children )
  213. child->UpdateScore( aMatcher, aLib, aFilter );
  214. }
  215. LIB_TREE_NODE_LIBRARY::LIB_TREE_NODE_LIBRARY( LIB_TREE_NODE* aParent, wxString const& aName,
  216. wxString const& aDesc )
  217. {
  218. m_Type = TYPE::LIBRARY;
  219. m_Name = aName;
  220. m_Desc = aDesc;
  221. m_Parent = aParent;
  222. m_LibId.SetLibNickname( aName );
  223. m_SearchTerms.emplace_back( SEARCH_TERM( aName, 8 ) );
  224. }
  225. LIB_TREE_NODE_ITEM& LIB_TREE_NODE_LIBRARY::AddItem( LIB_TREE_ITEM* aItem )
  226. {
  227. LIB_TREE_NODE_ITEM* item = new LIB_TREE_NODE_ITEM( this, aItem );
  228. m_Children.push_back( std::unique_ptr<LIB_TREE_NODE>( item ) );
  229. return *item;
  230. }
  231. void LIB_TREE_NODE_LIBRARY::UpdateScore( EDA_COMBINED_MATCHER* aMatcher, const wxString& aLib,
  232. std::function<bool( LIB_TREE_NODE& aNode )>* aFilter )
  233. {
  234. int maxChildScore = 0;
  235. // children are additive
  236. if( !m_Children.empty() )
  237. {
  238. for( std::unique_ptr<LIB_TREE_NODE>& child: m_Children )
  239. {
  240. child->UpdateScore( aMatcher, aLib, aFilter );
  241. maxChildScore = std::max( maxChildScore, child->m_Score );
  242. }
  243. m_Score = std::max( m_Score, maxChildScore );
  244. }
  245. // aLib test is additive
  246. if( !aLib.IsEmpty() && m_Name.Lower().Matches( aLib ) )
  247. m_Score += 1;
  248. // aMatcher test is additive
  249. if( aMatcher )
  250. {
  251. int ownScore = aMatcher->ScoreTerms( m_SearchTerms );
  252. m_Score += ownScore;
  253. // If we have a hit on a library, show all children in that library that pass the filter
  254. if( maxChildScore <= 0 && ownScore > 0 )
  255. {
  256. for( std::unique_ptr<LIB_TREE_NODE>& child: m_Children )
  257. {
  258. if( !aFilter || (*aFilter)( *this ) )
  259. child->ForceScore( 1 );
  260. maxChildScore = std::max( maxChildScore, child->m_Score );
  261. }
  262. }
  263. }
  264. // If all children are excluded for one reason or another, drop the library from the list
  265. if( !m_Children.empty() && maxChildScore <= 0 )
  266. m_Score = 0;
  267. // show all nodes if no search/filter/etc. criteria are given
  268. if( m_Children.empty() && !aMatcher && aLib.IsEmpty() && ( !aFilter || (*aFilter)(*this) ) )
  269. m_Score = 1;
  270. }
  271. LIB_TREE_NODE_ROOT::LIB_TREE_NODE_ROOT()
  272. {
  273. m_Type = TYPE::ROOT;
  274. }
  275. LIB_TREE_NODE_LIBRARY& LIB_TREE_NODE_ROOT::AddLib( wxString const& aName, wxString const& aDesc )
  276. {
  277. LIB_TREE_NODE_LIBRARY* lib = new LIB_TREE_NODE_LIBRARY( this, aName, aDesc );
  278. m_Children.push_back( std::unique_ptr<LIB_TREE_NODE>( lib ) );
  279. return *lib;
  280. }
  281. void LIB_TREE_NODE_ROOT::RemoveLib( wxString const& aName )
  282. {
  283. m_Children.erase( std::remove_if( m_Children.begin(), m_Children.end(),
  284. [&]( std::unique_ptr<LIB_TREE_NODE>& aNode )
  285. {
  286. return aNode->m_Name == aName;
  287. } ),
  288. m_Children.end() );
  289. }
  290. void LIB_TREE_NODE_ROOT::Clear()
  291. {
  292. m_Children.clear();
  293. }
  294. void LIB_TREE_NODE_ROOT::UpdateScore( EDA_COMBINED_MATCHER* aMatcher, const wxString& aLib,
  295. std::function<bool( LIB_TREE_NODE& aNode )>* aFilter )
  296. {
  297. for( std::unique_ptr<LIB_TREE_NODE>& child: m_Children )
  298. child->UpdateScore( aMatcher, aLib, aFilter );
  299. }