KiCad PCB EDA Suite
lib_tree_model_adapter.cpp
Go to the documentation of this file.
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 
22 #include <eda_base_frame.h>
23 #include <eda_pattern_match.h>
24 #include <kiface_i.h>
25 #include <config_params.h>
26 #include <lib_tree_model_adapter.h>
27 #include <project/project_file.h>
28 #include <settings/app_settings.h>
29 #include <wx/tokenzr.h>
30 #include <wx/wupdlock.h>
31 
32 
33 #define PINNED_ITEMS_KEY wxT( "PinnedItems" )
34 
35 static const int kDataViewIndent = 20;
36 
37 
41 wxDataViewItem LIB_TREE_MODEL_ADAPTER::ToItem( LIB_TREE_NODE const* aNode )
42 {
43  return wxDataViewItem( const_cast<void*>( static_cast<void const*>( aNode ) ) );
44 }
45 
46 
51 {
52  return static_cast<LIB_TREE_NODE*>( aItem.GetID() );
53 }
54 
55 
60  wxDataViewItemArray& aChildren )
61 {
62  unsigned int n = 0;
63 
64  for( auto const& child: aNode.m_Children )
65  {
66  if( child->m_Score > 0 )
67  {
68  aChildren.Add( ToItem( &*child ) );
69  ++n;
70  }
71  }
72 
73  return n;
74 }
75 
76 
78  m_parent( aParent ),
79  m_filter( CMP_FILTER_NONE ),
80  m_show_units( true ),
81  m_preselect_unit( 0 ),
82  m_freeze( 0 ),
83  m_col_part( nullptr ),
84  m_col_desc( nullptr ),
85  m_widget( nullptr ),
86  m_pinnedLibs(),
87  m_pinnedKey( aPinnedKey )
88 {
89  // Default column widths
90  m_colWidths[PART_COL] = 360;
91  m_colWidths[DESC_COL] = 2000;
92 
93  auto cfg = Kiface().KifaceSettings();
94  m_colWidths[PART_COL] = cfg->m_LibTree.column_width;
95 
96  // Read the pinned entries from the project config
97  PROJECT_FILE& project = m_parent->Kiway().Prj().GetProjectFile();
98 
99  std::vector<wxString>& entries = ( m_pinnedKey == "pinned_symbol_libs" ) ?
100  project.m_PinnedSymbolLibs :
101  project.m_PinnedFootprintLibs;
102 
103  for( const wxString& entry : entries )
104  m_pinnedLibs.push_back( entry );
105 }
106 
107 
109 {}
110 
111 
113 {
114  if( m_widget )
115  {
116  auto cfg = Kiface().KifaceSettings();
117  cfg->m_LibTree.column_width = m_widget->GetColumn( PART_COL )->GetWidth();
118  }
119  else
120  {
121  wxLogDebug( "Error saving column size, tree view doesn't exist" );
122  }
123 }
124 
125 
127 {
128  PROJECT_FILE& project = m_parent->Kiway().Prj().GetProjectFile();
129 
130  std::vector<wxString>& entries = ( m_pinnedKey == "pinned_symbol_libs" ) ?
131  project.m_PinnedSymbolLibs :
132  project.m_PinnedFootprintLibs;
133 
134  entries.clear();
135  m_pinnedLibs.clear();
136 
137  for( auto& child: m_tree.m_Children )
138  {
139  if( child->m_Pinned )
140  {
141  m_pinnedLibs.push_back( child->m_LibId.GetLibNickname() );
142  entries.push_back( child->m_LibId.GetLibNickname() );
143  }
144  }
145 
146 
147 }
148 
149 
151 {
152  m_filter = aFilter;
153 }
154 
155 
157 {
158  m_show_units = aShow;
159 }
160 
161 
162 void LIB_TREE_MODEL_ADAPTER::SetPreselectNode( LIB_ID const& aLibId, int aUnit )
163 {
164  m_preselect_lib_id = aLibId;
165  m_preselect_unit = aUnit;
166 }
167 
168 
170  wxString const& aDesc )
171 {
172  LIB_TREE_NODE_LIB& lib_node = m_tree.AddLib( aNodeName, aDesc );
173 
174  lib_node.m_Pinned = m_pinnedLibs.Index( lib_node.m_LibId.GetLibNickname() ) != wxNOT_FOUND;
175 
176  return lib_node;
177 }
178 
179 
180 void LIB_TREE_MODEL_ADAPTER::DoAddLibrary( wxString const& aNodeName, wxString const& aDesc,
181  std::vector<LIB_TREE_ITEM*> const& aItemList,
182  bool presorted )
183 {
184  LIB_TREE_NODE_LIB& lib_node = DoAddLibraryNode( aNodeName, aDesc );
185 
186  for( LIB_TREE_ITEM* item: aItemList )
187  lib_node.AddItem( item );
188 
189  lib_node.AssignIntrinsicRanks( presorted );
190 }
191 
192 
193 void LIB_TREE_MODEL_ADAPTER::UpdateSearchString( wxString const& aSearch )
194 {
195  m_tree.ResetScore();
196 
197  for( auto& child: m_tree.m_Children )
198  {
199  if( child->m_Pinned )
200  child->m_Score *= 2;
201  }
202 
203  wxStringTokenizer tokenizer( aSearch );
204 
205  while( tokenizer.HasMoreTokens() )
206  {
207  const wxString term = tokenizer.GetNextToken().Lower();
208  EDA_COMBINED_MATCHER matcher( term );
209 
210  m_tree.UpdateScore( matcher );
211  }
212 
213  m_tree.SortNodes();
214 
215  {
216  wxWindowUpdateLocker updateLock( m_widget );
217 
218  Freeze();
219  // Even with the updateLock, wxWidgets sometimes ties its knickers in
220  // a knot when trying to run a wxdataview_selection_changed_callback()
221  // on a row that has been deleted.
222  // https://bugs.launchpad.net/kicad/+bug/1756255
223  m_widget->UnselectAll();
224 
225  Cleared();
226  Thaw();
227 
228  // This was fixed in wxWidgets 3.0.5 and 3.1.3.
229 #if defined( __WXGTK__ ) && ( (wxVERSION_NUMBER < 030005 ) || \
230  ( ( wxVERSION_NUMBER >= 030100 ) && ( wxVERSION_NUMBER < 030103 ) ) )
231  // The fastest method to update wxDataViewCtrl is to rebuild from
232  // scratch by calling Cleared(). Linux requires to reassociate model to
233  // display data, but Windows will create multiple associations.
234  // On MacOS, this crashes kicad. See https://gitlab.com/kicad/code/kicad/issues/3666
235  // and https://gitlab.com/kicad/code/kicad/issues/3653
236  AttachTo( m_widget );
237 #endif
238  }
239 
240  LIB_TREE_NODE* bestMatch = ShowResults();
241 
242  if( !bestMatch )
243  bestMatch = ShowPreselect();
244 
245  if( !bestMatch )
246  bestMatch = ShowSingleLibrary();
247 
248  if( bestMatch )
249  {
250  auto item = wxDataViewItem( bestMatch );
251  m_widget->Select( item );
252 
253  // Make sure the *parent* item is visible. The selected item is the
254  // first (shown) child of the parent. So it's always right below the parent,
255  // and this way the user can also see what library the selected part belongs to,
256  // without having a case where the selection is off the screen (unless the
257  // window is a single row high, which is unlikely)
258  //
259  // This also happens to circumvent https://bugs.launchpad.net/kicad/+bug/1804400
260  // which appears to be a GTK+3 bug.
261  {
262  wxDataViewItem parent = GetParent( item );
263 
264  if( parent.IsOk() )
265  item = parent;
266  }
267 
268  m_widget->EnsureVisible( item );
269  }
270 }
271 
272 
273 void LIB_TREE_MODEL_ADAPTER::AttachTo( wxDataViewCtrl* aDataViewCtrl )
274 {
275  wxString partHead = _( "Item" );
276  wxString descHead = _( "Description" );
277 
278  // The extent of the text doesn't take into account the space on either side
279  // in the header, so artificially pad it by M
280  wxSize partHeadMinWidth = GetTextSize( partHead + "M", aDataViewCtrl );
281 
282  if( aDataViewCtrl->GetColumnCount() > 0 )
283  {
284  int partWidth = aDataViewCtrl->GetColumn( PART_COL )->GetWidth();
285  int descWidth = aDataViewCtrl->GetColumn( DESC_COL )->GetWidth();
286 
287  // Only use the widths read back if they are non-zero.
288  // GTK returns the displayed width of the column, which is not calculated immediately
289  // this leads to cases of 0 column width if the user types too fast in the filter
290  if( descWidth > 0 )
291  {
292  m_colWidths[PART_COL] = partWidth;
293  m_colWidths[DESC_COL] = descWidth;
294  }
295  }
296 
297  m_widget = aDataViewCtrl;
298  aDataViewCtrl->SetIndent( kDataViewIndent );
299  aDataViewCtrl->AssociateModel( this );
300  aDataViewCtrl->ClearColumns();
301 
302  m_col_part = aDataViewCtrl->AppendTextColumn( partHead, PART_COL, wxDATAVIEW_CELL_INERT,
304  m_col_desc = aDataViewCtrl->AppendTextColumn( descHead, DESC_COL, wxDATAVIEW_CELL_INERT,
306 
307  // Ensure the part column is wider than the smallest allowable width
308  if( m_colWidths[PART_COL] < partHeadMinWidth.x )
309  {
310  m_colWidths[PART_COL] = partHeadMinWidth.x;
311  m_col_part->SetWidth( partHeadMinWidth.x );
312  }
313 
314  m_col_part->SetMinWidth( partHeadMinWidth.x );
315 }
316 
317 
318 LIB_ID LIB_TREE_MODEL_ADAPTER::GetAliasFor( const wxDataViewItem& aSelection ) const
319 {
320  const LIB_TREE_NODE* node = ToNode( aSelection );
321 
322  LIB_ID emptyId;
323 
324  if( !node )
325  return emptyId;
326 
327  return node->m_LibId;
328 }
329 
330 
331 int LIB_TREE_MODEL_ADAPTER::GetUnitFor( const wxDataViewItem& aSelection ) const
332 {
333  const LIB_TREE_NODE* node = ToNode( aSelection );
334  return node ? node->m_Unit : 0;
335 }
336 
337 
338 LIB_TREE_NODE::TYPE LIB_TREE_MODEL_ADAPTER::GetTypeFor( const wxDataViewItem& aSelection ) const
339 {
340  const LIB_TREE_NODE* node = ToNode( aSelection );
341  return node ? node->m_Type : LIB_TREE_NODE::INVALID;
342 }
343 
344 
345 LIB_TREE_NODE* LIB_TREE_MODEL_ADAPTER::GetTreeNodeFor( const wxDataViewItem& aSelection ) const
346 {
347  return ToNode( aSelection );
348 }
349 
350 
352 {
353  int n = 0;
354 
355  for( const std::unique_ptr<LIB_TREE_NODE>& lib: m_tree.m_Children )
356  n += lib->m_Children.size();
357 
358  return n;
359 }
360 
361 
362 wxDataViewItem LIB_TREE_MODEL_ADAPTER::FindItem( const LIB_ID& aLibId )
363 {
364  for( auto& lib: m_tree.m_Children )
365  {
366  if( lib->m_Name != aLibId.GetLibNickname() )
367  continue;
368 
369  // if part name is not specified, return the library node
370  if( aLibId.GetLibItemName() == "" )
371  return ToItem( lib.get() );
372 
373  for( auto& alias: lib->m_Children )
374  {
375  if( alias->m_Name == aLibId.GetLibItemName() )
376  return ToItem( alias.get() );
377  }
378 
379  break; // could not find the part in the requested library
380  }
381 
382  return wxDataViewItem();
383 }
384 
385 
386 unsigned int LIB_TREE_MODEL_ADAPTER::GetChildren( wxDataViewItem const& aItem,
387  wxDataViewItemArray& aChildren ) const
388 {
389  auto node = ( aItem.IsOk() ? ToNode( aItem ) : &m_tree );
390 
391  if( node->m_Type != LIB_TREE_NODE::TYPE::LIBID
392  || ( m_show_units && node->m_Type == LIB_TREE_NODE::TYPE::LIBID ) )
393  return IntoArray( *node, aChildren );
394  else
395  return 0;
396 }
397 
398 
400 {
401  // Yes, this is an enormous hack. But it works on all platforms, it doesn't suffer
402  // the On^2 sorting issues that ItemChanged() does on OSX, and it doesn't lose the
403  // user's scroll position (which re-attaching or deleting/re-inserting columns does).
404  static int walk = 1;
405 
406  int partWidth = m_col_part->GetWidth();
407  int descWidth = m_col_desc->GetWidth();
408 
409  // Only use the widths read back if they are non-zero.
410  // GTK returns the displayed width of the column, which is not calculated immediately
411  if( descWidth > 0 )
412  {
413  m_colWidths[PART_COL] = partWidth;
414  m_colWidths[DESC_COL] = descWidth;
415  }
416 
417  m_colWidths[PART_COL] += walk;
418  m_colWidths[DESC_COL] -= walk;
419 
420  m_col_part->SetWidth( m_colWidths[PART_COL] );
421  m_col_desc->SetWidth( m_colWidths[DESC_COL] );
422  walk = -walk;
423 }
424 
425 
426 bool LIB_TREE_MODEL_ADAPTER::HasContainerColumns( wxDataViewItem const& aItem ) const
427 {
428  return IsContainer( aItem );
429 }
430 
431 
432 bool LIB_TREE_MODEL_ADAPTER::IsContainer( wxDataViewItem const& aItem ) const
433 {
434  auto node = ToNode( aItem );
435  return node ? node->m_Children.size() : true;
436 }
437 
438 
439 wxDataViewItem LIB_TREE_MODEL_ADAPTER::GetParent( wxDataViewItem const& aItem ) const
440 {
441  if( m_freeze )
442  return ToItem( nullptr );
443 
444  auto node = ToNode( aItem );
445  auto parent = node ? node->m_Parent : nullptr;
446 
447  // wxDataViewModel has no root node, but rather top-level elements have
448  // an invalid (null) parent.
449  if( !node || !parent || parent->m_Type == LIB_TREE_NODE::TYPE::ROOT )
450  return ToItem( nullptr );
451  else
452  return ToItem( parent );
453 }
454 
455 
456 void LIB_TREE_MODEL_ADAPTER::GetValue( wxVariant& aVariant,
457  wxDataViewItem const& aItem,
458  unsigned int aCol ) const
459 {
460  if( IsFrozen() )
461  {
462  aVariant = wxEmptyString;
463  return;
464  }
465 
466  auto node = ToNode( aItem );
467  wxASSERT( node );
468 
469  switch( aCol )
470  {
471  default: // column == -1 is used for default Compare function
472  case 0:
473  aVariant = node->m_Name;
474  break;
475  case 1:
476  aVariant = node->m_Desc;
477  break;
478  }
479 }
480 
481 
482 bool LIB_TREE_MODEL_ADAPTER::GetAttr( wxDataViewItem const& aItem,
483  unsigned int aCol,
484  wxDataViewItemAttr& aAttr ) const
485 {
486  if( IsFrozen() )
487  return false;
488 
489  auto node = ToNode( aItem );
490  wxASSERT( node );
491 
492  if( node->m_Type != LIB_TREE_NODE::LIBID )
493  {
494  // Currently only aliases are formatted at all
495  return false;
496  }
497 
498  if( !node->m_IsRoot && aCol == 0 )
499  {
500  // Names of non-root aliases are italicized
501  aAttr.SetItalic( true );
502  return true;
503  }
504  else
505  {
506  return false;
507  }
508 }
509 
510 
512  std::function<bool( LIB_TREE_NODE const* )> aFunc,
513  LIB_TREE_NODE** aHighScore )
514 {
515  for( auto& node: aNode.m_Children )
516  {
517  if( aFunc( &*node ) )
518  {
519  auto item = wxDataViewItem( &*node );
520  m_widget->ExpandAncestors( item );
521 
522  if( !(*aHighScore) || node->m_Score > (*aHighScore)->m_Score )
523  (*aHighScore) = &*node;
524  }
525 
526  FindAndExpand( *node, aFunc, aHighScore );
527  }
528 }
529 
530 
532 {
533  LIB_TREE_NODE* highScore = nullptr;
534 
536  []( LIB_TREE_NODE const* n )
537  {
538  // return leaf nodes with some level of matching
539  return n->m_Type == LIB_TREE_NODE::TYPE::LIBID && n->m_Score > 1;
540  },
541  &highScore );
542 
543  return highScore;
544 }
545 
546 
548 {
549  LIB_TREE_NODE* highScore = nullptr;
550 
551  if( !m_preselect_lib_id.IsValid() )
552  return highScore;
553 
555  [&]( LIB_TREE_NODE const* n )
556  {
557  if( n->m_Type == LIB_TREE_NODE::LIBID && ( n->m_Children.empty() || !m_preselect_unit ) )
558  return m_preselect_lib_id == n->m_LibId;
559  else if( n->m_Type == LIB_TREE_NODE::UNIT && m_preselect_unit )
561  else
562  return false;
563  },
564  &highScore );
565 
566  return highScore;
567 }
568 
569 
571 {
572  LIB_TREE_NODE* highScore = nullptr;
573 
575  []( LIB_TREE_NODE const* n )
576  {
577  return n->m_Type == LIB_TREE_NODE::TYPE::LIBID &&
578  n->m_Parent->m_Parent->m_Children.size() == 1;
579  },
580  &highScore );
581 
582  return highScore;
583 }
void DoAddLibrary(wxString const &aNodeName, wxString const &aDesc, std::vector< LIB_TREE_ITEM * > const &aItemList, bool presorted)
Add the given list of components by alias.
std::vector< wxString > m_PinnedSymbolLibs
Below are project-level settings that have not been moved to a dedicated file.
Definition: project_file.h:123
const UTF8 & GetLibItemName() const
Definition: lib_id.h:114
void SortNodes()
Sort child nodes quickly and recursively (IntrinsicRanks must have been set).
std::vector< wxString > m_PinnedFootprintLibs
The list of pinned footprint libraries.
Definition: project_file.h:126
bool IsContainer(wxDataViewItem const &aItem) const override
Check whether an item can have children.
KIWAY & Kiway() const
Function Kiway returns a reference to the KIWAY that this object has an opportunity to participate in...
Definition: kiway_holder.h:56
CMP_FILTER_TYPE
This enum allows a selective filtering of components to list.
A mix-in to provide polymorphism between items stored in libraries (symbols, aliases and footprints).
Definition: lib_tree_item.h:39
VTBL_ENTRY PROJECT & Prj() const
Function Prj returns the PROJECT associated with this KIWAY.
Definition: kiway.cpp:172
int GetUnitFor(const wxDataViewItem &aSelection) const
Return the unit for the given item.
void SetPreselectNode(LIB_ID const &aLibId, int aUnit)
Set the component name to be selected if there are no search results.
void UpdateSearchString(wxString const &aSearch)
Set the search string provided by the user.
LIB_TREE_NODE * ShowResults()
Find and expand successful search results.
APP_SETTINGS_BASE * KifaceSettings() const
Definition: kiface_i.h:103
LIB_TREE_NODE::TYPE GetTypeFor(const wxDataViewItem &aSelection) const
Return node type for the given item.
LIB_TREE_NODE * ShowPreselect()
Find and expand preselected node.
A logical library item identifier and consists of various portions much like a URI.
Definition: lib_id.h:51
bool IsValid() const
Definition: lib_id.h:171
PROJECT_FILE is the backing store for a PROJECT, in JSON format.
Definition: project_file.h:62
static wxDataViewItem ToItem(LIB_TREE_NODE const *aNode)
Convert CMP_TREE_NODE -> wxDataViewItem.
Abstract pattern-matching tool and implementations.
LIB_TREE_NODE * GetTreeNodeFor(const wxDataViewItem &aSelection) const
wxSize GetTextSize(const wxString &aSingleLine, wxWindow *aWindow)
Return the size of aSingleLine of text when it is rendered in aWindow using whatever font is currentl...
Definition: common.cpp:268
static unsigned int IntoArray(LIB_TREE_NODE const &aNode, wxDataViewItemArray &aChildren)
Convert CMP_TREE_NODE's children to wxDataViewItemArray.
bool GetAttr(wxDataViewItem const &aItem, unsigned int aCol, wxDataViewItemAttr &aAttr) const override
Get any formatting for an item.
static const int kDataViewIndent
int GetItemCount() const
Return the number of components loaded in the tree.
bool HasContainerColumns(wxDataViewItem const &aItem) const override
Check whether a container has columns too.
LIB_TREE_NODE_LIB_ID & AddItem(LIB_TREE_ITEM *aItem)
Construct a new alias node, add it to this library, and return it.
void FindAndExpand(LIB_TREE_NODE &aNode, std::function< bool(LIB_TREE_NODE const *)> aFunc, LIB_TREE_NODE **aHighScore)
Find any results worth highlighting and expand them, according to given criteria The highest-scoring ...
VTBL_ENTRY PROJECT_FILE & GetProjectFile() const
Definition: project.h:137
LIB_TREE_NODE * m_Parent
KIFACE_I & Kiface()
Global KIFACE_I "get" accessor.
Node type: library.
void SetFilter(CMP_FILTER_TYPE aFilter)
Set the component filter type.
Base window classes and related definitions.
static LIB_TREE_NODE * ToNode(wxDataViewItem aItem)
Convert wxDataViewItem -> CMP_TREE_NODE.
const UTF8 & GetLibNickname() const
Return the logical library name portion of a LIB_ID.
Definition: lib_id.h:97
wxDataViewItem GetParent(wxDataViewItem const &aItem) const override
Get the parent of an item.
void ResetScore()
Initialize score to kLowestDefaultScore, recursively.
Model class in the component selector Model-View-Adapter (mediated MVC) architecture.
LIB_TREE_NODE_LIB & DoAddLibraryNode(wxString const &aNodeName, wxString const &aDesc)
LIB_TREE_NODE_LIB & AddLib(wxString const &aName, wxString const &aDesc)
Construct an empty library node, add it to the root, and return it.
LIB_ID GetAliasFor(const wxDataViewItem &aSelection) const
Return the alias for the given item.
void AssignIntrinsicRanks(bool presorted=false)
Store intrinsic ranks on all children of this node.
void GetValue(wxVariant &aVariant, wxDataViewItem const &aItem, unsigned int aCol) const override
Get the value of an item.
#define _(s)
Definition: 3d_actions.cpp:33
enum TYPE m_Type
The base frame for deriving all KiCad main window classes.
wxDataViewItem FindItem(const LIB_ID &aLibId)
Returns tree item corresponding to part.
virtual void UpdateScore(EDA_COMBINED_MATCHER &aMatcher) override
Update the score for this part.
LIB_TREE_NODE * ShowSingleLibrary()
Find and expand a library if there is only one.
PTR_VECTOR m_Children
void SaveColWidths()
Save the column widths to the config file.
unsigned int GetChildren(wxDataViewItem const &aItem, wxDataViewItemArray &aChildren) const override
Populate a list of all the children of an item.
void AttachTo(wxDataViewCtrl *aDataViewCtrl)
Attach to a wxDataViewCtrl and initialize it.
void ShowUnits(bool aShow)
Whether or not to show units.
LIB_TREE_MODEL_ADAPTER(EDA_BASE_FRAME *aParent, wxString aPinnedKey)
Creates the adapter.