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 == "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 == "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  const wxString term = tokenizer.GetNextToken().Lower();
228  EDA_COMBINED_MATCHER matcher( term );
229 
230  m_tree.UpdateScore( matcher );
231  }
232 
233  m_tree.SortNodes();
234  AfterReset();
235  Thaw();
236  }
237 
238  LIB_TREE_NODE* bestMatch = ShowResults();
239 
240  if( !bestMatch )
241  bestMatch = ShowPreselect();
242 
243  if( !bestMatch )
244  bestMatch = ShowSingleLibrary();
245 
246  if( bestMatch )
247  {
248  wxDataViewItem item = wxDataViewItem( bestMatch );
249  m_widget->Select( item );
250 
251  // Make sure the *parent* item is visible. The selected item is the
252  // first (shown) child of the parent. So it's always right below the parent,
253  // and this way the user can also see what library the selected part belongs to,
254  // without having a case where the selection is off the screen (unless the
255  // window is a single row high, which is unlikely)
256  //
257  // This also happens to circumvent https://bugs.launchpad.net/kicad/+bug/1804400
258  // which appears to be a GTK+3 bug.
259  {
260  wxDataViewItem parent = GetParent( item );
261 
262  if( parent.IsOk() )
263  item = parent;
264  }
265 
266  m_widget->EnsureVisible( item );
267  }
268 }
269 
270 
271 void LIB_TREE_MODEL_ADAPTER::AttachTo( wxDataViewCtrl* aDataViewCtrl )
272 {
273  wxString partHead = _( "Item" );
274  wxString descHead = _( "Description" );
275 
276  // The extent of the text doesn't take into account the space on either side
277  // in the header, so artificially pad it
278  wxSize partHeadMinWidth = KIUI::GetTextSize( partHead + "MMM", aDataViewCtrl );
279 
280  // Ensure the part column is wider than the smallest allowable width
281  if( m_colWidths[PART_COL] < partHeadMinWidth.x )
282  m_colWidths[PART_COL] = partHeadMinWidth.x;
283 
284  m_widget = aDataViewCtrl;
285  aDataViewCtrl->SetIndent( kDataViewIndent );
286  aDataViewCtrl->AssociateModel( this );
287  aDataViewCtrl->ClearColumns();
288 
289  m_col_part = aDataViewCtrl->AppendTextColumn( partHead, PART_COL, wxDATAVIEW_CELL_INERT,
291  m_col_desc = aDataViewCtrl->AppendTextColumn( descHead, DESC_COL, wxDATAVIEW_CELL_INERT,
293 
294  m_col_part->SetMinWidth( partHeadMinWidth.x );
295 }
296 
297 
298 LIB_ID LIB_TREE_MODEL_ADAPTER::GetAliasFor( const wxDataViewItem& aSelection ) const
299 {
300  const LIB_TREE_NODE* node = ToNode( aSelection );
301 
302  LIB_ID emptyId;
303 
304  if( !node )
305  return emptyId;
306 
307  return node->m_LibId;
308 }
309 
310 
311 int LIB_TREE_MODEL_ADAPTER::GetUnitFor( const wxDataViewItem& aSelection ) const
312 {
313  const LIB_TREE_NODE* node = ToNode( aSelection );
314  return node ? node->m_Unit : 0;
315 }
316 
317 
318 LIB_TREE_NODE::TYPE LIB_TREE_MODEL_ADAPTER::GetTypeFor( const wxDataViewItem& aSelection ) const
319 {
320  const LIB_TREE_NODE* node = ToNode( aSelection );
321  return node ? node->m_Type : LIB_TREE_NODE::INVALID;
322 }
323 
324 
325 LIB_TREE_NODE* LIB_TREE_MODEL_ADAPTER::GetTreeNodeFor( const wxDataViewItem& aSelection ) const
326 {
327  return ToNode( aSelection );
328 }
329 
330 
332 {
333  int n = 0;
334 
335  for( const std::unique_ptr<LIB_TREE_NODE>& lib: m_tree.m_Children )
336  n += lib->m_Children.size();
337 
338  return n;
339 }
340 
341 
342 wxDataViewItem LIB_TREE_MODEL_ADAPTER::FindItem( const LIB_ID& aLibId )
343 {
344  for( std::unique_ptr<LIB_TREE_NODE>& lib: m_tree.m_Children )
345  {
346  if( lib->m_Name != aLibId.GetLibNickname() )
347  continue;
348 
349  // if part name is not specified, return the library node
350  if( aLibId.GetLibItemName() == "" )
351  return ToItem( lib.get() );
352 
353  for( std::unique_ptr<LIB_TREE_NODE>& alias: lib->m_Children )
354  {
355  if( alias->m_Name == aLibId.GetLibItemName() )
356  return ToItem( alias.get() );
357  }
358 
359  break; // could not find the part in the requested library
360  }
361 
362  return wxDataViewItem();
363 }
364 
365 
366 unsigned int LIB_TREE_MODEL_ADAPTER::GetChildren( const wxDataViewItem& aItem,
367  wxDataViewItemArray& aChildren ) const
368 {
369  const LIB_TREE_NODE* node = ( aItem.IsOk() ? ToNode( aItem ) : &m_tree );
370 
371  if( node->m_Type == LIB_TREE_NODE::TYPE::ROOT
372  || node->m_Type == LIB_TREE_NODE::LIB
373  || ( m_show_units && node->m_Type == LIB_TREE_NODE::TYPE::LIBID ) )
374  {
375  return IntoArray( *node, aChildren );
376  }
377  else
378  {
379  return 0;
380  }
381 
382 }
383 
384 
386 {
387  m_col_part->SetWidth( m_colWidths[PART_COL] );
388  m_col_desc->SetWidth( m_colWidths[DESC_COL] );
389 }
390 
391 
393 {
394  // Yes, this is an enormous hack. But it works on all platforms, it doesn't suffer
395  // the On^2 sorting issues that ItemChanged() does on OSX, and it doesn't lose the
396  // user's scroll position (which re-attaching or deleting/re-inserting columns does).
397  static int walk = 1;
398 
399  int partWidth = m_col_part->GetWidth();
400  int descWidth = m_col_desc->GetWidth();
401 
402  // Only use the widths read back if they are non-zero.
403  // GTK returns the displayed width of the column, which is not calculated immediately
404  if( descWidth > 0 )
405  {
406  m_colWidths[PART_COL] = partWidth;
407  m_colWidths[DESC_COL] = descWidth;
408  }
409 
410  m_colWidths[PART_COL] += walk;
411  m_colWidths[DESC_COL] -= walk;
412 
413  m_col_part->SetWidth( m_colWidths[PART_COL] );
414  m_col_desc->SetWidth( m_colWidths[DESC_COL] );
415  walk = -walk;
416 }
417 
418 
419 bool LIB_TREE_MODEL_ADAPTER::HasContainerColumns( const wxDataViewItem& aItem ) const
420 {
421  return IsContainer( aItem );
422 }
423 
424 
425 bool LIB_TREE_MODEL_ADAPTER::IsContainer( const wxDataViewItem& aItem ) const
426 {
427  LIB_TREE_NODE* node = ToNode( aItem );
428  return node ? node->m_Children.size() : true;
429 }
430 
431 
432 wxDataViewItem LIB_TREE_MODEL_ADAPTER::GetParent( const wxDataViewItem& aItem ) const
433 {
434  if( m_freeze )
435  return ToItem( nullptr );
436 
437  LIB_TREE_NODE* node = ToNode( aItem );
438  LIB_TREE_NODE* parent = node ? node->m_Parent : nullptr;
439 
440  // wxDataViewModel has no root node, but rather top-level elements have
441  // an invalid (null) parent.
442  if( !node || !parent || parent->m_Type == LIB_TREE_NODE::TYPE::ROOT )
443  return ToItem( nullptr );
444  else
445  return ToItem( parent );
446 }
447 
448 
449 void LIB_TREE_MODEL_ADAPTER::GetValue( wxVariant& aVariant,
450  const wxDataViewItem& aItem,
451  unsigned int aCol ) const
452 {
453  if( IsFrozen() )
454  {
455  aVariant = wxEmptyString;
456  return;
457  }
458 
459  LIB_TREE_NODE* node = ToNode( aItem );
460  wxASSERT( node );
461 
462  switch( aCol )
463  {
464  default: // column == -1 is used for default Compare function
465  case 0:
466  aVariant = UnescapeString( node->m_Name );
467  break;
468  case 1:
469  aVariant = node->m_Desc;
470  break;
471  }
472 }
473 
474 
475 bool LIB_TREE_MODEL_ADAPTER::GetAttr( const wxDataViewItem& aItem,
476  unsigned int aCol,
477  wxDataViewItemAttr& aAttr ) const
478 {
479  if( IsFrozen() )
480  return false;
481 
482  LIB_TREE_NODE* node = ToNode( aItem );
483  wxASSERT( node );
484 
485  if( node->m_Type != LIB_TREE_NODE::LIBID )
486  {
487  // Currently only aliases are formatted at all
488  return false;
489  }
490 
491  if( !node->m_IsRoot && aCol == 0 )
492  {
493  // Names of non-root aliases are italicized
494  aAttr.SetItalic( true );
495  return true;
496  }
497  else
498  {
499  return false;
500  }
501 }
502 
503 
505  std::function<bool( const LIB_TREE_NODE* )> aFunc,
506  LIB_TREE_NODE** aHighScore )
507 {
508  for( std::unique_ptr<LIB_TREE_NODE>& node: aNode.m_Children )
509  {
510  if( aFunc( &*node ) )
511  {
512  wxDataViewItem item = wxDataViewItem( &*node );
513  m_widget->ExpandAncestors( item );
514 
515  if( !(*aHighScore) || node->m_Score > (*aHighScore)->m_Score )
516  (*aHighScore) = &*node;
517  }
518 
519  FindAndExpand( *node, aFunc, aHighScore );
520  }
521 }
522 
523 
525 {
526  LIB_TREE_NODE* highScore = nullptr;
527 
529  []( LIB_TREE_NODE const* n )
530  {
531  // return leaf nodes with some level of matching
532  return n->m_Type == LIB_TREE_NODE::TYPE::LIBID && n->m_Score > 1;
533  },
534  &highScore );
535 
536  return highScore;
537 }
538 
539 
541 {
542  LIB_TREE_NODE* highScore = nullptr;
543 
544  if( !m_preselect_lib_id.IsValid() )
545  return highScore;
546 
548  [&]( LIB_TREE_NODE const* n )
549  {
550  if( n->m_Type == LIB_TREE_NODE::LIBID && ( n->m_Children.empty() ||
551  !m_preselect_unit ) )
552  return m_preselect_lib_id == n->m_LibId;
553  else if( n->m_Type == LIB_TREE_NODE::UNIT && m_preselect_unit )
554  return m_preselect_lib_id == n->m_Parent->m_LibId &&
555  m_preselect_unit == n->m_Unit;
556  else
557  return false;
558  },
559  &highScore );
560 
561  return highScore;
562 }
563 
564 
566 {
567  LIB_TREE_NODE* highScore = nullptr;
568 
570  []( LIB_TREE_NODE const* n )
571  {
572  return n->m_Type == LIB_TREE_NODE::TYPE::LIBID &&
573  n->m_Parent->m_Parent->m_Children.size() == 1;
574  },
575  &highScore );
576 
577  return highScore;
578 }
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:190
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.
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.
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.
void AttachTo(wxDataViewCtrl *aDataViewCtrl)
Attach to a wxDataViewCtrl and initialize it.
void ShowUnits(bool aShow)
Whether or not to show units.