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.

572 lines
16 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-2020 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 <eda_base_frame.h>
  22. #include <eda_pattern_match.h>
  23. #include <kiface_i.h>
  24. #include <config_params.h>
  25. #include <lib_tree_model_adapter.h>
  26. #include <project/project_file.h>
  27. #include <settings/app_settings.h>
  28. #include <wx/tokenzr.h>
  29. #include <wx/wupdlock.h>
  30. #define PINNED_ITEMS_KEY wxT( "PinnedItems" )
  31. static const int kDataViewIndent = 20;
  32. /**
  33. * Convert CMP_TREE_NODE -> wxDataViewItem
  34. */
  35. wxDataViewItem LIB_TREE_MODEL_ADAPTER::ToItem( LIB_TREE_NODE const* aNode )
  36. {
  37. return wxDataViewItem( const_cast<void*>( static_cast<void const*>( aNode ) ) );
  38. }
  39. /**
  40. * Convert wxDataViewItem -> CMP_TREE_NODE
  41. */
  42. LIB_TREE_NODE* LIB_TREE_MODEL_ADAPTER::ToNode( wxDataViewItem aItem )
  43. {
  44. return static_cast<LIB_TREE_NODE*>( aItem.GetID() );
  45. }
  46. /**
  47. * Convert CMP_TREE_NODE's children to wxDataViewItemArray
  48. */
  49. unsigned int LIB_TREE_MODEL_ADAPTER::IntoArray( LIB_TREE_NODE const& aNode,
  50. wxDataViewItemArray& aChildren )
  51. {
  52. unsigned int n = 0;
  53. for( auto const& child: aNode.m_Children )
  54. {
  55. if( child->m_Score > 0 )
  56. {
  57. aChildren.Add( ToItem( &*child ) );
  58. ++n;
  59. }
  60. }
  61. return n;
  62. }
  63. LIB_TREE_MODEL_ADAPTER::LIB_TREE_MODEL_ADAPTER( EDA_BASE_FRAME* aParent, wxString aPinnedKey ) :
  64. m_parent( aParent ),
  65. m_filter( CMP_FILTER_NONE ),
  66. m_show_units( true ),
  67. m_preselect_unit( 0 ),
  68. m_freeze( 0 ),
  69. m_col_part( nullptr ),
  70. m_col_desc( nullptr ),
  71. m_widget( nullptr ),
  72. m_pinnedLibs(),
  73. m_pinnedKey( aPinnedKey )
  74. {
  75. // Default column widths
  76. m_colWidths[PART_COL] = 360;
  77. m_colWidths[DESC_COL] = 2000;
  78. auto cfg = Kiface().KifaceSettings();
  79. m_colWidths[PART_COL] = cfg->m_LibTree.column_width;
  80. // Read the pinned entries from the project config
  81. PROJECT_FILE& project = m_parent->Kiway().Prj().GetProjectFile();
  82. std::vector<wxString>& entries = ( m_pinnedKey == "pinned_symbol_libs" ) ?
  83. project.m_PinnedSymbolLibs :
  84. project.m_PinnedFootprintLibs;
  85. for( const wxString& entry : entries )
  86. m_pinnedLibs.push_back( entry );
  87. }
  88. LIB_TREE_MODEL_ADAPTER::~LIB_TREE_MODEL_ADAPTER()
  89. {}
  90. void LIB_TREE_MODEL_ADAPTER::SaveColWidths()
  91. {
  92. if( m_widget )
  93. {
  94. auto cfg = Kiface().KifaceSettings();
  95. cfg->m_LibTree.column_width = m_widget->GetColumn( PART_COL )->GetWidth();
  96. }
  97. }
  98. void LIB_TREE_MODEL_ADAPTER::SavePinnedItems()
  99. {
  100. PROJECT_FILE& project = m_parent->Kiway().Prj().GetProjectFile();
  101. std::vector<wxString>& entries = ( m_pinnedKey == "pinned_symbol_libs" ) ?
  102. project.m_PinnedSymbolLibs :
  103. project.m_PinnedFootprintLibs;
  104. entries.clear();
  105. m_pinnedLibs.clear();
  106. for( auto& child: m_tree.m_Children )
  107. {
  108. if( child->m_Pinned )
  109. {
  110. m_pinnedLibs.push_back( child->m_LibId.GetLibNickname() );
  111. entries.push_back( child->m_LibId.GetLibNickname() );
  112. }
  113. }
  114. }
  115. void LIB_TREE_MODEL_ADAPTER::SetFilter( CMP_FILTER_TYPE aFilter )
  116. {
  117. m_filter = aFilter;
  118. }
  119. void LIB_TREE_MODEL_ADAPTER::ShowUnits( bool aShow )
  120. {
  121. m_show_units = aShow;
  122. }
  123. void LIB_TREE_MODEL_ADAPTER::SetPreselectNode( LIB_ID const& aLibId, int aUnit )
  124. {
  125. m_preselect_lib_id = aLibId;
  126. m_preselect_unit = aUnit;
  127. }
  128. LIB_TREE_NODE_LIB& LIB_TREE_MODEL_ADAPTER::DoAddLibraryNode( wxString const& aNodeName,
  129. wxString const& aDesc )
  130. {
  131. LIB_TREE_NODE_LIB& lib_node = m_tree.AddLib( aNodeName, aDesc );
  132. lib_node.m_Pinned = m_pinnedLibs.Index( lib_node.m_LibId.GetLibNickname() ) != wxNOT_FOUND;
  133. return lib_node;
  134. }
  135. void LIB_TREE_MODEL_ADAPTER::DoAddLibrary( wxString const& aNodeName, wxString const& aDesc,
  136. std::vector<LIB_TREE_ITEM*> const& aItemList,
  137. bool presorted )
  138. {
  139. LIB_TREE_NODE_LIB& lib_node = DoAddLibraryNode( aNodeName, aDesc );
  140. for( LIB_TREE_ITEM* item: aItemList )
  141. lib_node.AddItem( item );
  142. lib_node.AssignIntrinsicRanks( presorted );
  143. }
  144. void LIB_TREE_MODEL_ADAPTER::UpdateSearchString( wxString const& aSearch )
  145. {
  146. {
  147. // DO NOT REMOVE THE FREEZE/THAW. This freeze/thaw is a flag for this model adapter
  148. // that tells it when it shouldn't trust any of the data in the model. When set, it will
  149. // not return invalid data to the UI, since this invalid data can cause crashes.
  150. // This is different than the update locker, which locks the UI aspects only.
  151. wxWindowUpdateLocker updateLock( m_widget );
  152. BeforeReset();
  153. // Even with the updateLock, wxWidgets sometimes ties its knickers in
  154. // a knot when trying to run a wxdataview_selection_changed_callback()
  155. // on a row that has been deleted.
  156. // https://bugs.launchpad.net/kicad/+bug/1756255
  157. m_widget->UnselectAll();
  158. Freeze();
  159. m_tree.ResetScore();
  160. for( auto& child: m_tree.m_Children )
  161. {
  162. if( child->m_Pinned )
  163. child->m_Score *= 2;
  164. }
  165. wxStringTokenizer tokenizer( aSearch );
  166. while( tokenizer.HasMoreTokens() )
  167. {
  168. const wxString term = tokenizer.GetNextToken().Lower();
  169. EDA_COMBINED_MATCHER matcher( term );
  170. m_tree.UpdateScore( matcher );
  171. }
  172. m_tree.SortNodes();
  173. AfterReset();
  174. Thaw();
  175. }
  176. LIB_TREE_NODE* bestMatch = ShowResults();
  177. if( !bestMatch )
  178. bestMatch = ShowPreselect();
  179. if( !bestMatch )
  180. bestMatch = ShowSingleLibrary();
  181. if( bestMatch )
  182. {
  183. auto item = wxDataViewItem( bestMatch );
  184. m_widget->Select( item );
  185. // Make sure the *parent* item is visible. The selected item is the
  186. // first (shown) child of the parent. So it's always right below the parent,
  187. // and this way the user can also see what library the selected part belongs to,
  188. // without having a case where the selection is off the screen (unless the
  189. // window is a single row high, which is unlikely)
  190. //
  191. // This also happens to circumvent https://bugs.launchpad.net/kicad/+bug/1804400
  192. // which appears to be a GTK+3 bug.
  193. {
  194. wxDataViewItem parent = GetParent( item );
  195. if( parent.IsOk() )
  196. item = parent;
  197. }
  198. m_widget->EnsureVisible( item );
  199. }
  200. }
  201. void LIB_TREE_MODEL_ADAPTER::AttachTo( wxDataViewCtrl* aDataViewCtrl )
  202. {
  203. wxString partHead = _( "Item" );
  204. wxString descHead = _( "Description" );
  205. // The extent of the text doesn't take into account the space on either side
  206. // in the header, so artificially pad it by M
  207. wxSize partHeadMinWidth = GetTextSize( partHead + "M", aDataViewCtrl );
  208. if( aDataViewCtrl->GetColumnCount() > 0 )
  209. {
  210. int partWidth = aDataViewCtrl->GetColumn( PART_COL )->GetWidth();
  211. int descWidth = aDataViewCtrl->GetColumn( DESC_COL )->GetWidth();
  212. // Only use the widths read back if they are non-zero.
  213. // GTK returns the displayed width of the column, which is not calculated immediately
  214. // this leads to cases of 0 column width if the user types too fast in the filter
  215. if( descWidth > 0 )
  216. {
  217. m_colWidths[PART_COL] = partWidth;
  218. m_colWidths[DESC_COL] = descWidth;
  219. }
  220. }
  221. m_widget = aDataViewCtrl;
  222. aDataViewCtrl->SetIndent( kDataViewIndent );
  223. aDataViewCtrl->AssociateModel( this );
  224. aDataViewCtrl->ClearColumns();
  225. m_col_part = aDataViewCtrl->AppendTextColumn( partHead, PART_COL, wxDATAVIEW_CELL_INERT,
  226. m_colWidths[PART_COL] );
  227. m_col_desc = aDataViewCtrl->AppendTextColumn( descHead, DESC_COL, wxDATAVIEW_CELL_INERT,
  228. m_colWidths[DESC_COL] );
  229. // Ensure the part column is wider than the smallest allowable width
  230. if( m_colWidths[PART_COL] < partHeadMinWidth.x )
  231. {
  232. m_colWidths[PART_COL] = partHeadMinWidth.x;
  233. m_col_part->SetWidth( partHeadMinWidth.x );
  234. }
  235. m_col_part->SetMinWidth( partHeadMinWidth.x );
  236. }
  237. LIB_ID LIB_TREE_MODEL_ADAPTER::GetAliasFor( const wxDataViewItem& aSelection ) const
  238. {
  239. const LIB_TREE_NODE* node = ToNode( aSelection );
  240. LIB_ID emptyId;
  241. if( !node )
  242. return emptyId;
  243. return node->m_LibId;
  244. }
  245. int LIB_TREE_MODEL_ADAPTER::GetUnitFor( const wxDataViewItem& aSelection ) const
  246. {
  247. const LIB_TREE_NODE* node = ToNode( aSelection );
  248. return node ? node->m_Unit : 0;
  249. }
  250. LIB_TREE_NODE::TYPE LIB_TREE_MODEL_ADAPTER::GetTypeFor( const wxDataViewItem& aSelection ) const
  251. {
  252. const LIB_TREE_NODE* node = ToNode( aSelection );
  253. return node ? node->m_Type : LIB_TREE_NODE::INVALID;
  254. }
  255. LIB_TREE_NODE* LIB_TREE_MODEL_ADAPTER::GetTreeNodeFor( const wxDataViewItem& aSelection ) const
  256. {
  257. return ToNode( aSelection );
  258. }
  259. int LIB_TREE_MODEL_ADAPTER::GetItemCount() const
  260. {
  261. int n = 0;
  262. for( const std::unique_ptr<LIB_TREE_NODE>& lib: m_tree.m_Children )
  263. n += lib->m_Children.size();
  264. return n;
  265. }
  266. wxDataViewItem LIB_TREE_MODEL_ADAPTER::FindItem( const LIB_ID& aLibId )
  267. {
  268. for( auto& lib: m_tree.m_Children )
  269. {
  270. if( lib->m_Name != aLibId.GetLibNickname() )
  271. continue;
  272. // if part name is not specified, return the library node
  273. if( aLibId.GetLibItemName() == "" )
  274. return ToItem( lib.get() );
  275. for( auto& alias: lib->m_Children )
  276. {
  277. if( alias->m_Name == aLibId.GetLibItemName() )
  278. return ToItem( alias.get() );
  279. }
  280. break; // could not find the part in the requested library
  281. }
  282. return wxDataViewItem();
  283. }
  284. unsigned int LIB_TREE_MODEL_ADAPTER::GetChildren( wxDataViewItem const& aItem,
  285. wxDataViewItemArray& aChildren ) const
  286. {
  287. const LIB_TREE_NODE* node = ( aItem.IsOk() ? ToNode( aItem ) : &m_tree );
  288. if( node->m_Type != LIB_TREE_NODE::TYPE::LIBID
  289. || ( m_show_units && node->m_Type == LIB_TREE_NODE::TYPE::LIBID ) )
  290. return IntoArray( *node, aChildren );
  291. else
  292. return 0;
  293. }
  294. void LIB_TREE_MODEL_ADAPTER::RefreshTree()
  295. {
  296. // Yes, this is an enormous hack. But it works on all platforms, it doesn't suffer
  297. // the On^2 sorting issues that ItemChanged() does on OSX, and it doesn't lose the
  298. // user's scroll position (which re-attaching or deleting/re-inserting columns does).
  299. static int walk = 1;
  300. int partWidth = m_col_part->GetWidth();
  301. int descWidth = m_col_desc->GetWidth();
  302. // Only use the widths read back if they are non-zero.
  303. // GTK returns the displayed width of the column, which is not calculated immediately
  304. if( descWidth > 0 )
  305. {
  306. m_colWidths[PART_COL] = partWidth;
  307. m_colWidths[DESC_COL] = descWidth;
  308. }
  309. m_colWidths[PART_COL] += walk;
  310. m_colWidths[DESC_COL] -= walk;
  311. m_col_part->SetWidth( m_colWidths[PART_COL] );
  312. m_col_desc->SetWidth( m_colWidths[DESC_COL] );
  313. walk = -walk;
  314. }
  315. bool LIB_TREE_MODEL_ADAPTER::HasContainerColumns( wxDataViewItem const& aItem ) const
  316. {
  317. return IsContainer( aItem );
  318. }
  319. bool LIB_TREE_MODEL_ADAPTER::IsContainer( wxDataViewItem const& aItem ) const
  320. {
  321. LIB_TREE_NODE* node = ToNode( aItem );
  322. return node ? node->m_Children.size() : true;
  323. }
  324. wxDataViewItem LIB_TREE_MODEL_ADAPTER::GetParent( wxDataViewItem const& aItem ) const
  325. {
  326. if( m_freeze )
  327. return ToItem( nullptr );
  328. LIB_TREE_NODE* node = ToNode( aItem );
  329. LIB_TREE_NODE* parent = node ? node->m_Parent : nullptr;
  330. // wxDataViewModel has no root node, but rather top-level elements have
  331. // an invalid (null) parent.
  332. if( !node || !parent || parent->m_Type == LIB_TREE_NODE::TYPE::ROOT )
  333. return ToItem( nullptr );
  334. else
  335. return ToItem( parent );
  336. }
  337. void LIB_TREE_MODEL_ADAPTER::GetValue( wxVariant& aVariant,
  338. wxDataViewItem const& aItem,
  339. unsigned int aCol ) const
  340. {
  341. if( IsFrozen() )
  342. {
  343. aVariant = wxEmptyString;
  344. return;
  345. }
  346. LIB_TREE_NODE* node = ToNode( aItem );
  347. wxASSERT( node );
  348. switch( aCol )
  349. {
  350. default: // column == -1 is used for default Compare function
  351. case 0:
  352. aVariant = node->m_Name;
  353. break;
  354. case 1:
  355. aVariant = node->m_Desc;
  356. break;
  357. }
  358. }
  359. bool LIB_TREE_MODEL_ADAPTER::GetAttr( wxDataViewItem const& aItem,
  360. unsigned int aCol,
  361. wxDataViewItemAttr& aAttr ) const
  362. {
  363. if( IsFrozen() )
  364. return false;
  365. LIB_TREE_NODE* node = ToNode( aItem );
  366. wxASSERT( node );
  367. if( node->m_Type != LIB_TREE_NODE::LIBID )
  368. {
  369. // Currently only aliases are formatted at all
  370. return false;
  371. }
  372. if( !node->m_IsRoot && aCol == 0 )
  373. {
  374. // Names of non-root aliases are italicized
  375. aAttr.SetItalic( true );
  376. return true;
  377. }
  378. else
  379. {
  380. return false;
  381. }
  382. }
  383. void LIB_TREE_MODEL_ADAPTER::FindAndExpand( LIB_TREE_NODE& aNode,
  384. std::function<bool( LIB_TREE_NODE const* )> aFunc,
  385. LIB_TREE_NODE** aHighScore )
  386. {
  387. for( auto& node: aNode.m_Children )
  388. {
  389. if( aFunc( &*node ) )
  390. {
  391. auto item = wxDataViewItem( &*node );
  392. m_widget->ExpandAncestors( item );
  393. if( !(*aHighScore) || node->m_Score > (*aHighScore)->m_Score )
  394. (*aHighScore) = &*node;
  395. }
  396. FindAndExpand( *node, aFunc, aHighScore );
  397. }
  398. }
  399. LIB_TREE_NODE* LIB_TREE_MODEL_ADAPTER::ShowResults()
  400. {
  401. LIB_TREE_NODE* highScore = nullptr;
  402. FindAndExpand( m_tree,
  403. []( LIB_TREE_NODE const* n )
  404. {
  405. // return leaf nodes with some level of matching
  406. return n->m_Type == LIB_TREE_NODE::TYPE::LIBID && n->m_Score > 1;
  407. },
  408. &highScore );
  409. return highScore;
  410. }
  411. LIB_TREE_NODE* LIB_TREE_MODEL_ADAPTER::ShowPreselect()
  412. {
  413. LIB_TREE_NODE* highScore = nullptr;
  414. if( !m_preselect_lib_id.IsValid() )
  415. return highScore;
  416. FindAndExpand( m_tree,
  417. [&]( LIB_TREE_NODE const* n )
  418. {
  419. if( n->m_Type == LIB_TREE_NODE::LIBID && ( n->m_Children.empty() || !m_preselect_unit ) )
  420. return m_preselect_lib_id == n->m_LibId;
  421. else if( n->m_Type == LIB_TREE_NODE::UNIT && m_preselect_unit )
  422. return m_preselect_lib_id == n->m_Parent->m_LibId && m_preselect_unit == n->m_Unit;
  423. else
  424. return false;
  425. },
  426. &highScore );
  427. return highScore;
  428. }
  429. LIB_TREE_NODE* LIB_TREE_MODEL_ADAPTER::ShowSingleLibrary()
  430. {
  431. LIB_TREE_NODE* highScore = nullptr;
  432. FindAndExpand( m_tree,
  433. []( LIB_TREE_NODE const* n )
  434. {
  435. return n->m_Type == LIB_TREE_NODE::TYPE::LIBID &&
  436. n->m_Parent->m_Parent->m_Children.size() == 1;
  437. },
  438. &highScore );
  439. return highScore;
  440. }