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