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.

824 lines
24 KiB

2 years ago
2 years ago
2 years ago
2 years ago
3 years ago
3 years ago
3 years ago
3 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 The 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 <eda_base_frame.h>
  23. #include <eda_pattern_match.h>
  24. #include <kiface_base.h>
  25. #include <kiplatform/ui.h>
  26. #include <lib_tree_model_adapter.h>
  27. #include <project/project_file.h>
  28. #include <settings/app_settings.h>
  29. #include <widgets/ui_common.h>
  30. #include <wx/tokenzr.h>
  31. #include <wx/wupdlock.h>
  32. #include <wx/settings.h>
  33. #include <wx/dc.h>
  34. #include <string_utils.h>
  35. static const int kDataViewIndent = 20;
  36. class LIB_TREE_RENDERER : public wxDataViewCustomRenderer
  37. {
  38. public:
  39. LIB_TREE_RENDERER() :
  40. m_canvasItem( false )
  41. {}
  42. wxSize GetSize() const override
  43. {
  44. return wxSize( GetOwner()->GetWidth(), GetTextExtent( m_text ).y + 2 );
  45. }
  46. bool GetValue( wxVariant& aValue ) const override
  47. {
  48. aValue = m_text;
  49. return true;
  50. }
  51. bool SetValue( const wxVariant& aValue ) override
  52. {
  53. m_text = aValue.GetString();
  54. return true;
  55. }
  56. void SetAttr( const wxDataViewItemAttr& aAttr ) override
  57. {
  58. // Use strikethrough as a proxy for is-canvas-item
  59. m_canvasItem = aAttr.GetStrikethrough();
  60. wxDataViewItemAttr realAttr = aAttr;
  61. realAttr.SetStrikethrough( false );
  62. wxDataViewCustomRenderer::SetAttr( realAttr );
  63. }
  64. bool Render( wxRect aRect, wxDC *dc, int aState ) override
  65. {
  66. RenderBackground( dc, aRect );
  67. if( m_canvasItem )
  68. {
  69. wxPoint points[6];
  70. points[0] = aRect.GetTopLeft();
  71. points[1] = aRect.GetTopRight() + wxPoint( -4, 0 );
  72. points[2] = aRect.GetTopRight() + wxPoint( 0, aRect.GetHeight() / 2 );
  73. points[3] = aRect.GetBottomRight() + wxPoint( -4, 1 );
  74. points[4] = aRect.GetBottomLeft() + wxPoint( 0, 1 );
  75. points[5] = aRect.GetTopLeft();
  76. dc->SetPen( KIPLATFORM::UI::IsDarkTheme() ? *wxWHITE_PEN : *wxBLACK_PEN );
  77. dc->DrawLines( 6, points );
  78. }
  79. aRect.Deflate( 1 );
  80. #ifdef __WXOSX__
  81. // We should be able to pass wxDATAVIEW_CELL_SELECTED into RenderText() and have it do
  82. // the right thing -- but it picks wxSYS_COLOUR_HIGHLIGHTTEXT on MacOS (instead
  83. // of wxSYS_COLOUR_LISTBOXHIGHLIGHTTEXT).
  84. if( aState & wxDATAVIEW_CELL_SELECTED )
  85. dc->SetTextForeground( wxSystemSettings::GetColour( wxSYS_COLOUR_LISTBOXHIGHLIGHTTEXT ) );
  86. RenderText( m_text, 0, aRect, dc, 0 );
  87. #else
  88. RenderText( m_text, 0, aRect, dc, aState );
  89. #endif
  90. return true;
  91. }
  92. private:
  93. bool m_canvasItem;
  94. wxString m_text;
  95. };
  96. wxDataViewItem LIB_TREE_MODEL_ADAPTER::ToItem( const LIB_TREE_NODE* aNode )
  97. {
  98. return wxDataViewItem( const_cast<void*>( static_cast<void const*>( aNode ) ) );
  99. }
  100. LIB_TREE_NODE* LIB_TREE_MODEL_ADAPTER::ToNode( wxDataViewItem aItem )
  101. {
  102. return static_cast<LIB_TREE_NODE*>( aItem.GetID() );
  103. }
  104. LIB_TREE_MODEL_ADAPTER::LIB_TREE_MODEL_ADAPTER( EDA_BASE_FRAME* aParent,
  105. const wxString& aPinnedKey,
  106. APP_SETTINGS_BASE::LIB_TREE& aSettingsStruct ) :
  107. m_widget( nullptr ),
  108. m_parent( aParent ),
  109. m_cfg( aSettingsStruct ),
  110. m_sort_mode( BEST_MATCH ),
  111. m_show_units( true ),
  112. m_preselect_unit( 0 ),
  113. m_freeze( 0 ),
  114. m_filter( nullptr )
  115. {
  116. // Default column widths. Do not translate these names.
  117. m_colWidths[ _HKI( "Item" ) ] = 300;
  118. m_colWidths[ _HKI( "Description" ) ] = 600;
  119. m_availableColumns = { _HKI( "Item" ), _HKI( "Description" ) };
  120. for( const std::pair<const wxString, int>& pair : m_cfg.column_widths )
  121. m_colWidths[pair.first] = pair.second;
  122. m_shownColumns = m_cfg.columns;
  123. if( m_shownColumns.empty() )
  124. m_shownColumns = { _HKI( "Item" ), _HKI( "Description" ) };
  125. if( m_shownColumns[0] != _HKI( "Item" ) )
  126. m_shownColumns.insert( m_shownColumns.begin(), _HKI( "Item" ) );
  127. }
  128. LIB_TREE_MODEL_ADAPTER::~LIB_TREE_MODEL_ADAPTER()
  129. {}
  130. std::vector<wxString> LIB_TREE_MODEL_ADAPTER::GetOpenLibs() const
  131. {
  132. std::vector<wxString> openLibs;
  133. wxDataViewItem rootItem( nullptr );
  134. wxDataViewItemArray children;
  135. GetChildren( rootItem, children );
  136. for( const wxDataViewItem& child : children )
  137. {
  138. if( m_widget->IsExpanded( child ) )
  139. openLibs.emplace_back( ToNode( child )->m_LibId.GetLibNickname().wx_str() );
  140. }
  141. return openLibs;
  142. }
  143. void LIB_TREE_MODEL_ADAPTER::OpenLibs( const std::vector<wxString>& aLibs )
  144. {
  145. wxWindowUpdateLocker updateLock( m_widget );
  146. for( const wxString& lib : aLibs )
  147. {
  148. wxDataViewItem item = FindItem( LIB_ID( lib, wxEmptyString ) );
  149. if( item.IsOk() )
  150. m_widget->Expand( item );
  151. }
  152. }
  153. void LIB_TREE_MODEL_ADAPTER::SaveSettings()
  154. {
  155. if( m_widget )
  156. {
  157. m_cfg.columns = GetShownColumns();
  158. m_cfg.column_widths.clear();
  159. for( const std::pair<const wxString, wxDataViewColumn*>& pair : m_colNameMap )
  160. m_cfg.column_widths[pair.first] = pair.second->GetWidth();
  161. m_cfg.open_libs = GetOpenLibs();
  162. }
  163. }
  164. void LIB_TREE_MODEL_ADAPTER::ShowUnits( bool aShow )
  165. {
  166. m_show_units = aShow;
  167. }
  168. void LIB_TREE_MODEL_ADAPTER::SetPreselectNode( const LIB_ID& aLibId, int aUnit )
  169. {
  170. m_preselect_lib_id = aLibId;
  171. m_preselect_unit = aUnit;
  172. }
  173. LIB_TREE_NODE_LIBRARY& LIB_TREE_MODEL_ADAPTER::DoAddLibraryNode( const wxString& aNodeName,
  174. const wxString& aDesc,
  175. bool pinned )
  176. {
  177. LIB_TREE_NODE_LIBRARY& lib_node = m_tree.AddLib( aNodeName, aDesc );
  178. lib_node.m_Pinned = pinned;
  179. return lib_node;
  180. }
  181. LIB_TREE_NODE_LIBRARY& LIB_TREE_MODEL_ADAPTER::DoAddLibrary( const wxString& aNodeName,
  182. const wxString& aDesc,
  183. const std::vector<LIB_TREE_ITEM*>& aItemList,
  184. bool pinned, bool presorted )
  185. {
  186. LIB_TREE_NODE_LIBRARY& lib_node = DoAddLibraryNode( aNodeName, aDesc, pinned );
  187. for( LIB_TREE_ITEM* item: aItemList )
  188. lib_node.AddItem( item );
  189. lib_node.AssignIntrinsicRanks( presorted );
  190. return lib_node;
  191. }
  192. void LIB_TREE_MODEL_ADAPTER::RemoveGroup( bool aRecentGroup, bool aPlacedGroup )
  193. {
  194. m_tree.RemoveGroup( aRecentGroup, aPlacedGroup );
  195. }
  196. void LIB_TREE_MODEL_ADAPTER::UpdateSearchString( const wxString& aSearch, bool aState )
  197. {
  198. {
  199. wxWindowUpdateLocker updateLock( m_widget );
  200. // Even with the updateLock, wxWidgets sometimes ties its knickers in a knot trying to
  201. // run a wxdataview_selection_changed_callback() on a row that has been deleted.
  202. // https://bugs.launchpad.net/kicad/+bug/1756255
  203. m_widget->UnselectAll();
  204. // This collapse is required before the call to "Freeze()" below. Once Freeze()
  205. // is called, GetParent() will return nullptr. While this works for some calls, it
  206. // segfaults when we have any expanded elements b/c the sub units in the tree don't
  207. // have explicit references that are maintained over a search
  208. // The tree will be expanded again below when we get our matches
  209. //
  210. // Also note that this cannot happen when we have deleted a symbol as GTK will also
  211. // iterate over the tree in this case and find a symbol that has an invalid link
  212. // and crash https://gitlab.com/kicad/code/kicad/-/issues/6910
  213. if( !aState && !aSearch.IsNull() && m_tree.m_Children.size() )
  214. {
  215. for( std::unique_ptr<LIB_TREE_NODE>& child: m_tree.m_Children )
  216. m_widget->Collapse( wxDataViewItem( &*child ) );
  217. }
  218. // DO NOT REMOVE THE FREEZE/THAW. This freeze/thaw is a flag for this model adapter
  219. // that tells it when it shouldn't trust any of the data in the model. When set, it will
  220. // not return invalid data to the UI, since this invalid data can cause crashes.
  221. // This is different than the update locker, which locks the UI aspects only.
  222. Freeze();
  223. BeforeReset();
  224. m_tree.ResetScore();
  225. // Don't cause KiCad to hang if someone accidentally pastes the PCB or schematic into
  226. // the search box.
  227. constexpr int MAX_TERMS = 100;
  228. wxStringTokenizer tokenizer( aSearch );
  229. std::vector<std::unique_ptr<EDA_COMBINED_MATCHER>> termMatchers;
  230. while( tokenizer.HasMoreTokens() && termMatchers.size() < MAX_TERMS )
  231. {
  232. wxString term = tokenizer.GetNextToken().Lower();
  233. termMatchers.emplace_back( std::make_unique<EDA_COMBINED_MATCHER>( term, CTX_LIBITEM ) );
  234. }
  235. m_tree.UpdateScore( termMatchers, m_filter );
  236. m_tree.SortNodes( m_sort_mode == BEST_MATCH );
  237. AfterReset();
  238. Thaw();
  239. }
  240. const LIB_TREE_NODE* firstMatch = ShowResults();
  241. if( firstMatch )
  242. {
  243. wxDataViewItem item = ToItem( firstMatch );
  244. m_widget->Select( item );
  245. // Make sure the *parent* item is visible. The selected item is the first (shown) child
  246. // of the parent. So it's always right below the parent, and this way the user can also
  247. // see what library the selected part belongs to, without having a case where the selection
  248. // is off the screen (unless the window is a single row high, which is unlikely).
  249. //
  250. // This also happens to circumvent https://bugs.launchpad.net/kicad/+bug/1804400 which
  251. // appears to be a GTK+3 bug.
  252. {
  253. wxDataViewItem parent = GetParent( item );
  254. if( parent.IsOk() )
  255. m_widget->EnsureVisible( parent );
  256. }
  257. m_widget->EnsureVisible( item );
  258. }
  259. }
  260. void LIB_TREE_MODEL_ADAPTER::AttachTo( wxDataViewCtrl* aDataViewCtrl )
  261. {
  262. m_widget = aDataViewCtrl;
  263. aDataViewCtrl->SetIndent( kDataViewIndent );
  264. aDataViewCtrl->AssociateModel( this );
  265. recreateColumns();
  266. }
  267. void LIB_TREE_MODEL_ADAPTER::recreateColumns()
  268. {
  269. m_widget->ClearColumns();
  270. m_columns.clear();
  271. m_colIdxMap.clear();
  272. m_colNameMap.clear();
  273. // The Item column is always shown
  274. doAddColumn( wxT( "Item" ) );
  275. for( const wxString& colName : m_shownColumns )
  276. {
  277. if( !m_colNameMap.count( colName ) )
  278. doAddColumn( colName, colName == wxT( "Description" ) );
  279. }
  280. }
  281. void LIB_TREE_MODEL_ADAPTER::resortTree()
  282. {
  283. Freeze();
  284. BeforeReset();
  285. m_tree.SortNodes( m_sort_mode == BEST_MATCH );
  286. AfterReset();
  287. Thaw();
  288. }
  289. void LIB_TREE_MODEL_ADAPTER::PinLibrary( LIB_TREE_NODE* aTreeNode )
  290. {
  291. m_parent->Prj().PinLibrary( aTreeNode->m_LibId.GetLibNickname(), getLibType() );
  292. aTreeNode->m_Pinned = true;
  293. resortTree();
  294. m_widget->EnsureVisible( ToItem( aTreeNode ) );
  295. }
  296. void LIB_TREE_MODEL_ADAPTER::UnpinLibrary( LIB_TREE_NODE* aTreeNode )
  297. {
  298. m_parent->Prj().UnpinLibrary( aTreeNode->m_LibId.GetLibNickname(), getLibType() );
  299. aTreeNode->m_Pinned = false;
  300. resortTree();
  301. // Keep focus at top when unpinning
  302. }
  303. void LIB_TREE_MODEL_ADAPTER::ShowChangedLanguage()
  304. {
  305. recreateColumns();
  306. for( const std::unique_ptr<LIB_TREE_NODE>& lib: m_tree.m_Children )
  307. {
  308. if( lib->m_IsRecentlyUsedGroup )
  309. lib->m_Name = wxT( "-- " ) + _( "Recently Used" ) + wxT( " --" );
  310. else if( lib->m_IsAlreadyPlacedGroup )
  311. lib->m_Name = wxT( "-- " ) + _( "Already Placed" ) + wxT( " --" );
  312. }
  313. }
  314. wxDataViewColumn* LIB_TREE_MODEL_ADAPTER::doAddColumn( const wxString& aHeader, bool aTranslate )
  315. {
  316. wxString translatedHeader = aTranslate ? wxGetTranslation( aHeader ) : aHeader;
  317. // The extent of the text doesn't take into account the space on either side
  318. // in the header, so artificially pad it
  319. wxSize headerMinWidth = KIUI::GetTextSize( translatedHeader + wxT( "MMM" ), m_widget );
  320. if( !m_colWidths.count( aHeader ) || m_colWidths[aHeader] < headerMinWidth.x )
  321. m_colWidths[aHeader] = headerMinWidth.x;
  322. int index = (int) m_columns.size();
  323. wxDataViewColumn* col = new wxDataViewColumn(
  324. translatedHeader, new LIB_TREE_RENDERER(), index, m_colWidths[aHeader], wxALIGN_NOT,
  325. wxDATAVIEW_CELL_INERT | static_cast<int>( wxDATAVIEW_COL_RESIZABLE ) );
  326. m_widget->AppendColumn( col );
  327. col->SetMinWidth( headerMinWidth.x );
  328. m_columns.emplace_back( col );
  329. m_colNameMap[aHeader] = col;
  330. m_colIdxMap[m_columns.size() - 1] = aHeader;
  331. return col;
  332. }
  333. void LIB_TREE_MODEL_ADAPTER::addColumnIfNecessary( const wxString& aHeader )
  334. {
  335. if( m_colNameMap.count( aHeader ) )
  336. return;
  337. // Columns will be created later
  338. m_colNameMap[aHeader] = nullptr;
  339. m_availableColumns.emplace_back( aHeader );
  340. }
  341. void LIB_TREE_MODEL_ADAPTER::SetShownColumns( const std::vector<wxString>& aColumnNames )
  342. {
  343. bool recreate = m_shownColumns != aColumnNames;
  344. m_shownColumns = aColumnNames;
  345. if( recreate && m_widget )
  346. recreateColumns();
  347. }
  348. LIB_ID LIB_TREE_MODEL_ADAPTER::GetAliasFor( const wxDataViewItem& aSelection ) const
  349. {
  350. const LIB_TREE_NODE* node = ToNode( aSelection );
  351. return node ? node->m_LibId : LIB_ID();
  352. }
  353. int LIB_TREE_MODEL_ADAPTER::GetUnitFor( const wxDataViewItem& aSelection ) const
  354. {
  355. const LIB_TREE_NODE* node = ToNode( aSelection );
  356. return node ? node->m_Unit : 0;
  357. }
  358. LIB_TREE_NODE::TYPE LIB_TREE_MODEL_ADAPTER::GetTypeFor( const wxDataViewItem& aSelection ) const
  359. {
  360. const LIB_TREE_NODE* node = ToNode( aSelection );
  361. return node ? node->m_Type : LIB_TREE_NODE::TYPE::INVALID;
  362. }
  363. LIB_TREE_NODE* LIB_TREE_MODEL_ADAPTER::GetTreeNodeFor( const wxDataViewItem& aSelection ) const
  364. {
  365. return ToNode( aSelection );
  366. }
  367. int LIB_TREE_MODEL_ADAPTER::GetItemCount() const
  368. {
  369. int n = 0;
  370. for( const std::unique_ptr<LIB_TREE_NODE>& lib: m_tree.m_Children )
  371. n += lib->m_Children.size();
  372. return n;
  373. }
  374. wxDataViewItem LIB_TREE_MODEL_ADAPTER::FindItem( const LIB_ID& aLibId )
  375. {
  376. for( std::unique_ptr<LIB_TREE_NODE>& lib: m_tree.m_Children )
  377. {
  378. if( lib->m_Name != aLibId.GetLibNickname().wx_str() )
  379. continue;
  380. // if part name is not specified, return the library node
  381. if( aLibId.GetLibItemName() == "" )
  382. return ToItem( lib.get() );
  383. for( std::unique_ptr<LIB_TREE_NODE>& alias: lib->m_Children )
  384. {
  385. if( alias->m_Name == aLibId.GetLibItemName().wx_str() )
  386. return ToItem( alias.get() );
  387. }
  388. break; // could not find the part in the requested library
  389. }
  390. return wxDataViewItem();
  391. }
  392. wxDataViewItem LIB_TREE_MODEL_ADAPTER::GetCurrentDataViewItem()
  393. {
  394. return FindItem( m_preselect_lib_id );
  395. }
  396. unsigned int LIB_TREE_MODEL_ADAPTER::GetChildren( const wxDataViewItem& aItem,
  397. wxDataViewItemArray& aChildren ) const
  398. {
  399. const LIB_TREE_NODE* node = ( aItem.IsOk() ? ToNode( aItem ) : &m_tree );
  400. unsigned int count = 0;
  401. if( node->m_Type == LIB_TREE_NODE::TYPE::ROOT
  402. || node->m_Type == LIB_TREE_NODE::TYPE::LIBRARY
  403. || ( m_show_units && node->m_Type == LIB_TREE_NODE::TYPE::ITEM ) )
  404. {
  405. for( std::unique_ptr<LIB_TREE_NODE> const& child: node->m_Children )
  406. {
  407. if( child->m_Score > 0 )
  408. {
  409. aChildren.Add( ToItem( &*child ) );
  410. ++count;
  411. }
  412. }
  413. }
  414. return count;
  415. }
  416. void LIB_TREE_MODEL_ADAPTER::FinishTreeInitialization()
  417. {
  418. wxDataViewColumn* col = nullptr;
  419. size_t idx = 0;
  420. int totalWidth = 0;
  421. wxString header;
  422. for( ; idx < m_columns.size() - 1; idx++ )
  423. {
  424. wxASSERT( m_colIdxMap.count( idx ) );
  425. col = m_columns[idx];
  426. header = m_colIdxMap[idx];
  427. wxASSERT( m_colWidths.count( header ) );
  428. col->SetWidth( m_colWidths[header] );
  429. totalWidth += col->GetWidth();
  430. }
  431. int remainingWidth = m_widget->GetSize().x - totalWidth;
  432. header = m_columns[idx]->GetTitle();
  433. m_columns[idx]->SetWidth( std::max( m_colWidths[header], remainingWidth ) );
  434. }
  435. void LIB_TREE_MODEL_ADAPTER::RefreshTree()
  436. {
  437. // Yes, this is an enormous hack. But it works on all platforms, it doesn't suffer
  438. // the On^2 sorting issues that ItemChanged() does on OSX, and it doesn't lose the
  439. // user's scroll position (which re-attaching or deleting/re-inserting columns does).
  440. static int walk = 1;
  441. std::vector<int> widths;
  442. for( const wxDataViewColumn* col : m_columns )
  443. widths.emplace_back( col->GetWidth() );
  444. wxASSERT( widths.size() );
  445. // Only use the widths read back if they are non-zero.
  446. // GTK returns the displayed width of the column, which is not calculated immediately
  447. if( widths[0] > 0 )
  448. {
  449. size_t i = 0;
  450. for( const auto& [ colName, colPtr ] : m_colNameMap )
  451. m_colWidths[ colName ] = widths[i++];
  452. }
  453. auto colIt = m_colWidths.begin();
  454. colIt->second += walk;
  455. colIt++;
  456. if( colIt != m_colWidths.end() )
  457. colIt->second -= walk;
  458. for( const auto& [ colName, colPtr ] : m_colNameMap )
  459. {
  460. if( colPtr == m_columns[0] )
  461. continue;
  462. wxASSERT( m_colWidths.count( colName ) );
  463. colPtr->SetWidth( m_colWidths[ colName ] );
  464. }
  465. walk = -walk;
  466. }
  467. bool LIB_TREE_MODEL_ADAPTER::HasContainerColumns( const wxDataViewItem& aItem ) const
  468. {
  469. return IsContainer( aItem );
  470. }
  471. bool LIB_TREE_MODEL_ADAPTER::IsContainer( const wxDataViewItem& aItem ) const
  472. {
  473. LIB_TREE_NODE* node = ToNode( aItem );
  474. return node ? node->m_Children.size() : true;
  475. }
  476. wxDataViewItem LIB_TREE_MODEL_ADAPTER::GetParent( const wxDataViewItem& aItem ) const
  477. {
  478. if( m_freeze )
  479. return ToItem( nullptr );
  480. LIB_TREE_NODE* node = ToNode( aItem );
  481. LIB_TREE_NODE* parent = node ? node->m_Parent : nullptr;
  482. // wxDataViewModel has no root node, but rather top-level elements have
  483. // an invalid (null) parent.
  484. if( !node || !parent || parent->m_Type == LIB_TREE_NODE::TYPE::ROOT )
  485. return ToItem( nullptr );
  486. else
  487. return ToItem( parent );
  488. }
  489. void LIB_TREE_MODEL_ADAPTER::GetValue( wxVariant& aVariant,
  490. const wxDataViewItem& aItem,
  491. unsigned int aCol ) const
  492. {
  493. if( IsFrozen() )
  494. {
  495. aVariant = wxEmptyString;
  496. return;
  497. }
  498. LIB_TREE_NODE* node = ToNode( aItem );
  499. wxCHECK( node, /* void */ );
  500. wxString valueStr;
  501. switch( aCol )
  502. {
  503. case NAME_COL:
  504. if( node->m_Pinned )
  505. valueStr = GetPinningSymbol() + UnescapeString( node->m_Name );
  506. else
  507. valueStr = UnescapeString( node->m_Name );
  508. break;
  509. default:
  510. if( m_colIdxMap.count( aCol ) )
  511. {
  512. const wxString& key = m_colIdxMap.at( aCol );
  513. if( key == wxT( "Description" ) )
  514. valueStr = UnescapeString( node->m_Desc );
  515. else if( node->m_Fields.count( key ) )
  516. valueStr = UnescapeString( node->m_Fields.at( key ) );
  517. else
  518. valueStr = wxEmptyString;
  519. }
  520. break;
  521. }
  522. valueStr.Replace( wxS( "\n" ), wxS( " " ) ); // Clear line breaks
  523. aVariant = valueStr;
  524. }
  525. bool LIB_TREE_MODEL_ADAPTER::GetAttr( const wxDataViewItem& aItem,
  526. unsigned int aCol,
  527. wxDataViewItemAttr& aAttr ) const
  528. {
  529. if( IsFrozen() )
  530. return false;
  531. LIB_TREE_NODE* node = ToNode( aItem );
  532. wxCHECK( node, false );
  533. if( node->m_Type == LIB_TREE_NODE::TYPE::ITEM )
  534. {
  535. if( !node->m_IsRoot && aCol == 0 )
  536. {
  537. // Names of non-root aliases are italicized
  538. aAttr.SetItalic( true );
  539. return true;
  540. }
  541. }
  542. return false;
  543. }
  544. void recursiveDescent( LIB_TREE_NODE& aNode, const std::function<int( const LIB_TREE_NODE* )>& f )
  545. {
  546. for( std::unique_ptr<LIB_TREE_NODE>& node: aNode.m_Children )
  547. {
  548. int r = f( node.get() );
  549. if( r == 0 )
  550. break;
  551. else if( r == -1 )
  552. continue;
  553. recursiveDescent( *node, f );
  554. }
  555. }
  556. const LIB_TREE_NODE* LIB_TREE_MODEL_ADAPTER::ShowResults()
  557. {
  558. const LIB_TREE_NODE* firstMatch = nullptr;
  559. // Expand parents of leaf nodes with some level of matching
  560. recursiveDescent( m_tree,
  561. [&]( const LIB_TREE_NODE* n )
  562. {
  563. if( n->m_Type == LIB_TREE_NODE::TYPE::ITEM && n->m_Score > 1 )
  564. {
  565. if( !firstMatch )
  566. firstMatch = n;
  567. else if( n->m_Score > firstMatch->m_Score )
  568. firstMatch = n;
  569. m_widget->ExpandAncestors( ToItem( n ) );
  570. }
  571. return 1; // keep going to expand ancestors of all found items
  572. } );
  573. // If no matches, find and show the preselect node
  574. if( !firstMatch && m_preselect_lib_id.IsValid() )
  575. {
  576. recursiveDescent( m_tree,
  577. [&]( const LIB_TREE_NODE* n )
  578. {
  579. // Don't match the recent and already placed libraries
  580. if( n->m_Name.StartsWith( "-- " ) )
  581. return -1; // Skip this node and its children
  582. if( n->m_Type == LIB_TREE_NODE::TYPE::ITEM
  583. && ( n->m_Children.empty() || !m_preselect_unit )
  584. && m_preselect_lib_id == n->m_LibId )
  585. {
  586. firstMatch = n;
  587. m_widget->ExpandAncestors( ToItem( n ) );
  588. return 0;
  589. }
  590. else if( n->m_Type == LIB_TREE_NODE::TYPE::UNIT
  591. && ( m_preselect_unit && m_preselect_unit == n->m_Unit )
  592. && m_preselect_lib_id == n->m_Parent->m_LibId )
  593. {
  594. firstMatch = n;
  595. m_widget->ExpandAncestors( ToItem( n ) );
  596. return 0;
  597. }
  598. return 1;
  599. } );
  600. }
  601. // If still no matches expand a single library if there is only one
  602. if( !firstMatch )
  603. {
  604. int libraries = 0;
  605. for( const std::unique_ptr<LIB_TREE_NODE>& child : m_tree.m_Children )
  606. {
  607. if( !child->m_Name.StartsWith( "-- " ) )
  608. libraries++;
  609. }
  610. if( libraries != 1 )
  611. return nullptr;
  612. recursiveDescent( m_tree,
  613. [&]( const LIB_TREE_NODE* n )
  614. {
  615. if( n->m_Type == LIB_TREE_NODE::TYPE::ITEM )
  616. {
  617. firstMatch = n;
  618. m_widget->ExpandAncestors( ToItem( n ) );
  619. return 0;
  620. }
  621. return 1;
  622. } );
  623. }
  624. return firstMatch;
  625. }