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( auto 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 
94  auto cfg = Kiface().KifaceSettings();
95  m_colWidths[PART_COL] = cfg->m_LibTree.column_width;
96 
97  // Read the pinned entries from the project config
98  PROJECT_FILE& project = m_parent->Kiway().Prj().GetProjectFile();
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  {
117  auto cfg = Kiface().KifaceSettings();
118  cfg->m_LibTree.column_width = m_widget->GetColumn( PART_COL )->GetWidth();
119  }
120 }
121 
122 
124 {
125  PROJECT_FILE& project = m_parent->Kiway().Prj().GetProjectFile();
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( auto& 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 our first library expanded.
203  // The tree will be expanded again below when we get our matches
204  //
205  // Also note that this cannot happen when we have deleted a symbol as GTK will also
206  // iterate over the tree in this case and find a symbol that has an invalid link
207  // and crash https://gitlab.com/kicad/code/kicad/-/issues/6910
208  if( !aState && !aSearch.IsNull() && m_tree.m_Children.size() )
209  m_widget->Collapse( wxDataViewItem( &*m_tree.m_Children[0] ) );
210 
211  // DO NOT REMOVE THE FREEZE/THAW. This freeze/thaw is a flag for this model adapter
212  // that tells it when it shouldn't trust any of the data in the model. When set, it will
213  // not return invalid data to the UI, since this invalid data can cause crashes.
214  // This is different than the update locker, which locks the UI aspects only.
215  Freeze();
216  BeforeReset();
217 
218  m_tree.ResetScore();
219 
220  for( std::unique_ptr<LIB_TREE_NODE>& child: m_tree.m_Children )
221  {
222  if( child->m_Pinned )
223  child->m_Score *= 2;
224  }
225 
226  wxStringTokenizer tokenizer( aSearch );
227 
228  while( tokenizer.HasMoreTokens() )
229  {
230  const wxString term = tokenizer.GetNextToken().Lower();
231  EDA_COMBINED_MATCHER matcher( term );
232 
233  m_tree.UpdateScore( matcher );
234  }
235 
236  m_tree.SortNodes();
237  AfterReset();
238  Thaw();
239  }
240 
241  LIB_TREE_NODE* bestMatch = ShowResults();
242 
243  if( !bestMatch )
244  bestMatch = ShowPreselect();
245 
246  if( !bestMatch )
247  bestMatch = ShowSingleLibrary();
248 
249  if( bestMatch )
250  {
251  auto item = wxDataViewItem( bestMatch );
252  m_widget->Select( item );
253 
254  // Make sure the *parent* item is visible. The selected item is the
255  // first (shown) child of the parent. So it's always right below the parent,
256  // and this way the user can also see what library the selected part belongs to,
257  // without having a case where the selection is off the screen (unless the
258  // window is a single row high, which is unlikely)
259  //
260  // This also happens to circumvent https://bugs.launchpad.net/kicad/+bug/1804400
261  // which appears to be a GTK+3 bug.
262  {
263  wxDataViewItem parent = GetParent( item );
264 
265  if( parent.IsOk() )
266  item = parent;
267  }
268 
269  m_widget->EnsureVisible( item );
270  }
271 }
272 
273 
274 void LIB_TREE_MODEL_ADAPTER::AttachTo( wxDataViewCtrl* aDataViewCtrl )
275 {
276  wxString partHead = _( "Item" );
277  wxString descHead = _( "Description" );
278 
279  // The extent of the text doesn't take into account the space on either side
280  // in the header, so artificially pad it by M
281  wxSize partHeadMinWidth = KIUI::GetTextSize( partHead + "M", aDataViewCtrl );
282 
283  if( aDataViewCtrl->GetColumnCount() > 0 )
284  {
285  int partWidth = aDataViewCtrl->GetColumn( PART_COL )->GetWidth();
286  int descWidth = aDataViewCtrl->GetColumn( DESC_COL )->GetWidth();
287 
288  // Only use the widths read back if they are non-zero.
289  // GTK returns the displayed width of the column, which is not calculated immediately
290  // this leads to cases of 0 column width if the user types too fast in the filter
291  if( descWidth > 0 )
292  {
293  m_colWidths[PART_COL] = partWidth;
294  m_colWidths[DESC_COL] = descWidth;
295  }
296  }
297 
298  m_widget = aDataViewCtrl;
299  aDataViewCtrl->SetIndent( kDataViewIndent );
300  aDataViewCtrl->AssociateModel( this );
301  aDataViewCtrl->ClearColumns();
302 
303  m_col_part = aDataViewCtrl->AppendTextColumn( partHead, PART_COL, wxDATAVIEW_CELL_INERT,
305  m_col_desc = aDataViewCtrl->AppendTextColumn( descHead, DESC_COL, wxDATAVIEW_CELL_INERT,
307 
308  // Ensure the part column is wider than the smallest allowable width
309  if( m_colWidths[PART_COL] < partHeadMinWidth.x )
310  {
311  m_colWidths[PART_COL] = partHeadMinWidth.x;
312  m_col_part->SetWidth( partHeadMinWidth.x );
313  }
314 
315  m_col_part->SetMinWidth( partHeadMinWidth.x );
316 }
317 
318 
319 LIB_ID LIB_TREE_MODEL_ADAPTER::GetAliasFor( const wxDataViewItem& aSelection ) const
320 {
321  const LIB_TREE_NODE* node = ToNode( aSelection );
322 
323  LIB_ID emptyId;
324 
325  if( !node )
326  return emptyId;
327 
328  return node->m_LibId;
329 }
330 
331 
332 int LIB_TREE_MODEL_ADAPTER::GetUnitFor( const wxDataViewItem& aSelection ) const
333 {
334  const LIB_TREE_NODE* node = ToNode( aSelection );
335  return node ? node->m_Unit : 0;
336 }
337 
338 
339 LIB_TREE_NODE::TYPE LIB_TREE_MODEL_ADAPTER::GetTypeFor( const wxDataViewItem& aSelection ) const
340 {
341  const LIB_TREE_NODE* node = ToNode( aSelection );
342  return node ? node->m_Type : LIB_TREE_NODE::INVALID;
343 }
344 
345 
346 LIB_TREE_NODE* LIB_TREE_MODEL_ADAPTER::GetTreeNodeFor( const wxDataViewItem& aSelection ) const
347 {
348  return ToNode( aSelection );
349 }
350 
351 
353 {
354  int n = 0;
355 
356  for( const std::unique_ptr<LIB_TREE_NODE>& lib: m_tree.m_Children )
357  n += lib->m_Children.size();
358 
359  return n;
360 }
361 
362 
363 wxDataViewItem LIB_TREE_MODEL_ADAPTER::FindItem( const LIB_ID& aLibId )
364 {
365  for( auto& lib: m_tree.m_Children )
366  {
367  if( lib->m_Name != aLibId.GetLibNickname() )
368  continue;
369 
370  // if part name is not specified, return the library node
371  if( aLibId.GetLibItemName() == "" )
372  return ToItem( lib.get() );
373 
374  for( auto& alias: lib->m_Children )
375  {
376  if( alias->m_Name == aLibId.GetLibItemName() )
377  return ToItem( alias.get() );
378  }
379 
380  break; // could not find the part in the requested library
381  }
382 
383  return wxDataViewItem();
384 }
385 
386 
387 unsigned int LIB_TREE_MODEL_ADAPTER::GetChildren( wxDataViewItem const& aItem,
388  wxDataViewItemArray& aChildren ) const
389 {
390  const LIB_TREE_NODE* node = ( aItem.IsOk() ? ToNode( aItem ) : &m_tree );
391 
392  if( node->m_Type != LIB_TREE_NODE::TYPE::LIBID
393  || ( m_show_units && node->m_Type == LIB_TREE_NODE::TYPE::LIBID ) )
394  return IntoArray( *node, aChildren );
395  else
396  return 0;
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( wxDataViewItem const& aItem ) const
428 {
429  return IsContainer( aItem );
430 }
431 
432 
433 bool LIB_TREE_MODEL_ADAPTER::IsContainer( wxDataViewItem const& 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( wxDataViewItem const& 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  wxDataViewItem const& 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 = 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( wxDataViewItem const& 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( LIB_TREE_NODE const* )> aFunc,
514  LIB_TREE_NODE** aHighScore )
515 {
516  for( auto& node: aNode.m_Children )
517  {
518  if( aFunc( &*node ) )
519  {
520  auto 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() || !m_preselect_unit ) )
559  return m_preselect_lib_id == n->m_LibId;
560  else if( n->m_Type == LIB_TREE_NODE::UNIT && m_preselect_unit )
562  else
563  return false;
564  },
565  &highScore );
566 
567  return highScore;
568 }
569 
570 
572 {
573  LIB_TREE_NODE* highScore = nullptr;
574 
576  []( LIB_TREE_NODE const* n )
577  {
578  return n->m_Type == LIB_TREE_NODE::TYPE::LIBID &&
579  n->m_Parent->m_Parent->m_Children.size() == 1;
580  },
581  &highScore );
582 
583  return highScore;
584 }
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:106
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
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:174
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
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
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 ...
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.
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.