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