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 }
120 
121 
123 {
124  PROJECT_FILE& project = m_parent->Kiway().Prj().GetProjectFile();
125 
126  std::vector<wxString>& entries = ( m_pinnedKey == "pinned_symbol_libs" ) ?
127  project.m_PinnedSymbolLibs :
128  project.m_PinnedFootprintLibs;
129 
130  entries.clear();
131  m_pinnedLibs.clear();
132 
133  for( auto& child: m_tree.m_Children )
134  {
135  if( child->m_Pinned )
136  {
137  m_pinnedLibs.push_back( child->m_LibId.GetLibNickname() );
138  entries.push_back( child->m_LibId.GetLibNickname() );
139  }
140  }
141 
142 
143 }
144 
145 
147 {
148  m_filter = aFilter;
149 }
150 
151 
153 {
154  m_show_units = aShow;
155 }
156 
157 
158 void LIB_TREE_MODEL_ADAPTER::SetPreselectNode( LIB_ID const& aLibId, int aUnit )
159 {
160  m_preselect_lib_id = aLibId;
161  m_preselect_unit = aUnit;
162 }
163 
164 
166  wxString const& aDesc )
167 {
168  LIB_TREE_NODE_LIB& lib_node = m_tree.AddLib( aNodeName, aDesc );
169 
170  lib_node.m_Pinned = m_pinnedLibs.Index( lib_node.m_LibId.GetLibNickname() ) != wxNOT_FOUND;
171 
172  return lib_node;
173 }
174 
175 
176 void LIB_TREE_MODEL_ADAPTER::DoAddLibrary( wxString const& aNodeName, wxString const& aDesc,
177  std::vector<LIB_TREE_ITEM*> const& aItemList,
178  bool presorted )
179 {
180  LIB_TREE_NODE_LIB& lib_node = DoAddLibraryNode( aNodeName, aDesc );
181 
182  for( LIB_TREE_ITEM* item: aItemList )
183  lib_node.AddItem( item );
184 
185  lib_node.AssignIntrinsicRanks( presorted );
186 }
187 
188 
189 void LIB_TREE_MODEL_ADAPTER::UpdateSearchString( wxString const& aSearch )
190 {
191  {
192  // DO NOT REMOVE THE FREEZE/THAW. This freeze/thaw is a flag for this model adapter
193  // that tells it when it shouldn't trust any of the data in the model. When set, it will
194  // not return invalid data to the UI, since this invalid data can cause crashes.
195  // This is different than the update locker, which locks the UI aspects only.
196  wxWindowUpdateLocker updateLock( m_widget );
197  BeforeReset();
198 
199  // Even with the updateLock, wxWidgets sometimes ties its knickers in
200  // a knot when trying to run a wxdataview_selection_changed_callback()
201  // on a row that has been deleted.
202  // https://bugs.launchpad.net/kicad/+bug/1756255
203  m_widget->UnselectAll();
204  Freeze();
205 
206  m_tree.ResetScore();
207 
208  for( auto& child: m_tree.m_Children )
209  {
210  if( child->m_Pinned )
211  child->m_Score *= 2;
212  }
213 
214  wxStringTokenizer tokenizer( aSearch );
215 
216  while( tokenizer.HasMoreTokens() )
217  {
218  const wxString term = tokenizer.GetNextToken().Lower();
219  EDA_COMBINED_MATCHER matcher( term );
220 
221  m_tree.UpdateScore( matcher );
222  }
223 
224  m_tree.SortNodes();
225  AfterReset();
226  Thaw();
227  }
228 
229  LIB_TREE_NODE* bestMatch = ShowResults();
230 
231  if( !bestMatch )
232  bestMatch = ShowPreselect();
233 
234  if( !bestMatch )
235  bestMatch = ShowSingleLibrary();
236 
237  if( bestMatch )
238  {
239  auto item = wxDataViewItem( bestMatch );
240  m_widget->Select( item );
241 
242  // Make sure the *parent* item is visible. The selected item is the
243  // first (shown) child of the parent. So it's always right below the parent,
244  // and this way the user can also see what library the selected part belongs to,
245  // without having a case where the selection is off the screen (unless the
246  // window is a single row high, which is unlikely)
247  //
248  // This also happens to circumvent https://bugs.launchpad.net/kicad/+bug/1804400
249  // which appears to be a GTK+3 bug.
250  {
251  wxDataViewItem parent = GetParent( item );
252 
253  if( parent.IsOk() )
254  item = parent;
255  }
256 
257  m_widget->EnsureVisible( item );
258  }
259 }
260 
261 
262 void LIB_TREE_MODEL_ADAPTER::AttachTo( wxDataViewCtrl* aDataViewCtrl )
263 {
264  wxString partHead = _( "Item" );
265  wxString descHead = _( "Description" );
266 
267  // The extent of the text doesn't take into account the space on either side
268  // in the header, so artificially pad it by M
269  wxSize partHeadMinWidth = GetTextSize( partHead + "M", aDataViewCtrl );
270 
271  if( aDataViewCtrl->GetColumnCount() > 0 )
272  {
273  int partWidth = aDataViewCtrl->GetColumn( PART_COL )->GetWidth();
274  int descWidth = aDataViewCtrl->GetColumn( DESC_COL )->GetWidth();
275 
276  // Only use the widths read back if they are non-zero.
277  // GTK returns the displayed width of the column, which is not calculated immediately
278  // this leads to cases of 0 column width if the user types too fast in the filter
279  if( descWidth > 0 )
280  {
281  m_colWidths[PART_COL] = partWidth;
282  m_colWidths[DESC_COL] = descWidth;
283  }
284  }
285 
286  m_widget = aDataViewCtrl;
287  aDataViewCtrl->SetIndent( kDataViewIndent );
288  aDataViewCtrl->AssociateModel( this );
289  aDataViewCtrl->ClearColumns();
290 
291  m_col_part = aDataViewCtrl->AppendTextColumn( partHead, PART_COL, wxDATAVIEW_CELL_INERT,
293  m_col_desc = aDataViewCtrl->AppendTextColumn( descHead, DESC_COL, wxDATAVIEW_CELL_INERT,
295 
296  // Ensure the part column is wider than the smallest allowable width
297  if( m_colWidths[PART_COL] < partHeadMinWidth.x )
298  {
299  m_colWidths[PART_COL] = partHeadMinWidth.x;
300  m_col_part->SetWidth( partHeadMinWidth.x );
301  }
302 
303  m_col_part->SetMinWidth( partHeadMinWidth.x );
304 }
305 
306 
307 LIB_ID LIB_TREE_MODEL_ADAPTER::GetAliasFor( const wxDataViewItem& aSelection ) const
308 {
309  const LIB_TREE_NODE* node = ToNode( aSelection );
310 
311  LIB_ID emptyId;
312 
313  if( !node )
314  return emptyId;
315 
316  return node->m_LibId;
317 }
318 
319 
320 int LIB_TREE_MODEL_ADAPTER::GetUnitFor( const wxDataViewItem& aSelection ) const
321 {
322  const LIB_TREE_NODE* node = ToNode( aSelection );
323  return node ? node->m_Unit : 0;
324 }
325 
326 
327 LIB_TREE_NODE::TYPE LIB_TREE_MODEL_ADAPTER::GetTypeFor( const wxDataViewItem& aSelection ) const
328 {
329  const LIB_TREE_NODE* node = ToNode( aSelection );
330  return node ? node->m_Type : LIB_TREE_NODE::INVALID;
331 }
332 
333 
334 LIB_TREE_NODE* LIB_TREE_MODEL_ADAPTER::GetTreeNodeFor( const wxDataViewItem& aSelection ) const
335 {
336  return ToNode( aSelection );
337 }
338 
339 
341 {
342  int n = 0;
343 
344  for( const std::unique_ptr<LIB_TREE_NODE>& lib: m_tree.m_Children )
345  n += lib->m_Children.size();
346 
347  return n;
348 }
349 
350 
351 wxDataViewItem LIB_TREE_MODEL_ADAPTER::FindItem( const LIB_ID& aLibId )
352 {
353  for( auto& lib: m_tree.m_Children )
354  {
355  if( lib->m_Name != aLibId.GetLibNickname() )
356  continue;
357 
358  // if part name is not specified, return the library node
359  if( aLibId.GetLibItemName() == "" )
360  return ToItem( lib.get() );
361 
362  for( auto& alias: lib->m_Children )
363  {
364  if( alias->m_Name == aLibId.GetLibItemName() )
365  return ToItem( alias.get() );
366  }
367 
368  break; // could not find the part in the requested library
369  }
370 
371  return wxDataViewItem();
372 }
373 
374 
375 unsigned int LIB_TREE_MODEL_ADAPTER::GetChildren( wxDataViewItem const& aItem,
376  wxDataViewItemArray& aChildren ) const
377 {
378  const LIB_TREE_NODE* node = ( aItem.IsOk() ? ToNode( aItem ) : &m_tree );
379 
380  if( node->m_Type != LIB_TREE_NODE::TYPE::LIBID
381  || ( m_show_units && node->m_Type == LIB_TREE_NODE::TYPE::LIBID ) )
382  return IntoArray( *node, aChildren );
383  else
384  return 0;
385 }
386 
387 
389 {
390  // Yes, this is an enormous hack. But it works on all platforms, it doesn't suffer
391  // the On^2 sorting issues that ItemChanged() does on OSX, and it doesn't lose the
392  // user's scroll position (which re-attaching or deleting/re-inserting columns does).
393  static int walk = 1;
394 
395  int partWidth = m_col_part->GetWidth();
396  int descWidth = m_col_desc->GetWidth();
397 
398  // Only use the widths read back if they are non-zero.
399  // GTK returns the displayed width of the column, which is not calculated immediately
400  if( descWidth > 0 )
401  {
402  m_colWidths[PART_COL] = partWidth;
403  m_colWidths[DESC_COL] = descWidth;
404  }
405 
406  m_colWidths[PART_COL] += walk;
407  m_colWidths[DESC_COL] -= walk;
408 
409  m_col_part->SetWidth( m_colWidths[PART_COL] );
410  m_col_desc->SetWidth( m_colWidths[DESC_COL] );
411  walk = -walk;
412 }
413 
414 
415 bool LIB_TREE_MODEL_ADAPTER::HasContainerColumns( wxDataViewItem const& aItem ) const
416 {
417  return IsContainer( aItem );
418 }
419 
420 
421 bool LIB_TREE_MODEL_ADAPTER::IsContainer( wxDataViewItem const& aItem ) const
422 {
423  LIB_TREE_NODE* node = ToNode( aItem );
424  return node ? node->m_Children.size() : true;
425 }
426 
427 
428 wxDataViewItem LIB_TREE_MODEL_ADAPTER::GetParent( wxDataViewItem const& aItem ) const
429 {
430  if( m_freeze )
431  return ToItem( nullptr );
432 
433  LIB_TREE_NODE* node = ToNode( aItem );
434  LIB_TREE_NODE* parent = node ? node->m_Parent : nullptr;
435 
436  // wxDataViewModel has no root node, but rather top-level elements have
437  // an invalid (null) parent.
438  if( !node || !parent || parent->m_Type == LIB_TREE_NODE::TYPE::ROOT )
439  return ToItem( nullptr );
440  else
441  return ToItem( parent );
442 }
443 
444 
445 void LIB_TREE_MODEL_ADAPTER::GetValue( wxVariant& aVariant,
446  wxDataViewItem const& aItem,
447  unsigned int aCol ) const
448 {
449  if( IsFrozen() )
450  {
451  aVariant = wxEmptyString;
452  return;
453  }
454 
455  LIB_TREE_NODE* node = ToNode( aItem );
456  wxASSERT( node );
457 
458  switch( aCol )
459  {
460  default: // column == -1 is used for default Compare function
461  case 0:
462  aVariant = node->m_Name;
463  break;
464  case 1:
465  aVariant = node->m_Desc;
466  break;
467  }
468 }
469 
470 
471 bool LIB_TREE_MODEL_ADAPTER::GetAttr( wxDataViewItem const& aItem,
472  unsigned int aCol,
473  wxDataViewItemAttr& aAttr ) const
474 {
475  if( IsFrozen() )
476  return false;
477 
478  LIB_TREE_NODE* node = ToNode( aItem );
479  wxASSERT( node );
480 
481  if( node->m_Type != LIB_TREE_NODE::LIBID )
482  {
483  // Currently only aliases are formatted at all
484  return false;
485  }
486 
487  if( !node->m_IsRoot && aCol == 0 )
488  {
489  // Names of non-root aliases are italicized
490  aAttr.SetItalic( true );
491  return true;
492  }
493  else
494  {
495  return false;
496  }
497 }
498 
499 
501  std::function<bool( LIB_TREE_NODE const* )> aFunc,
502  LIB_TREE_NODE** aHighScore )
503 {
504  for( auto& node: aNode.m_Children )
505  {
506  if( aFunc( &*node ) )
507  {
508  auto item = wxDataViewItem( &*node );
509  m_widget->ExpandAncestors( item );
510 
511  if( !(*aHighScore) || node->m_Score > (*aHighScore)->m_Score )
512  (*aHighScore) = &*node;
513  }
514 
515  FindAndExpand( *node, aFunc, aHighScore );
516  }
517 }
518 
519 
521 {
522  LIB_TREE_NODE* highScore = nullptr;
523 
525  []( LIB_TREE_NODE const* n )
526  {
527  // return leaf nodes with some level of matching
528  return n->m_Type == LIB_TREE_NODE::TYPE::LIBID && n->m_Score > 1;
529  },
530  &highScore );
531 
532  return highScore;
533 }
534 
535 
537 {
538  LIB_TREE_NODE* highScore = nullptr;
539 
540  if( !m_preselect_lib_id.IsValid() )
541  return highScore;
542 
544  [&]( LIB_TREE_NODE const* n )
545  {
546  if( n->m_Type == LIB_TREE_NODE::LIBID && ( n->m_Children.empty() || !m_preselect_unit ) )
547  return m_preselect_lib_id == n->m_LibId;
548  else if( n->m_Type == LIB_TREE_NODE::UNIT && m_preselect_unit )
550  else
551  return false;
552  },
553  &highScore );
554 
555  return highScore;
556 }
557 
558 
560 {
561  LIB_TREE_NODE* highScore = nullptr;
562 
564  []( LIB_TREE_NODE const* n )
565  {
566  return n->m_Type == LIB_TREE_NODE::TYPE::LIBID &&
567  n->m_Parent->m_Parent->m_Children.size() == 1;
568  },
569  &highScore );
570 
571  return highScore;
572 }
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:173
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:297
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:141
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.
wxString m_Name
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
wxString m_Desc
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.