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 <settings/app_settings.h>
28 #include <wx/tokenzr.h>
29 #include <wx/wupdlock.h>
30 
31 
32 #define LIST_COLUMN_WIDTH_KEY wxT( "SelectorColumnWidth" )
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 {
87  // Default column widths
88  m_colWidths[PART_COL] = 360;
89  m_colWidths[DESC_COL] = 2000;
90 
91  auto cfg = Kiface().KifaceSettings();
92  m_colWidths[PART_COL] = cfg->m_LibTree.column_width;
93 
94  // Read the pinned entries from the project config
95  m_parent->Kiway().Prj().ConfigLoad( Kiface().KifaceSearch(), m_parent->GetName(),
97 }
98 
99 
101 {}
102 
103 
105 {
106  if( m_widget )
107  {
108  auto cfg = Kiface().KifaceSettings();
109  cfg->m_LibTree.column_width = m_widget->GetColumn( PART_COL )->GetWidth();
110  }
111  else
112  {
113  wxLogDebug( "Error saving column size, tree view doesn't exist" );
114  }
115 }
116 
117 
119 {
120  if( !m_projectFileParams.empty() )
121  return m_projectFileParams;
122 
124 
125  return m_projectFileParams;
126 }
127 
128 
130 {
131  m_pinnedLibs.clear();
132 
133  for( auto& child: m_tree.m_Children )
134  {
135  if( child->m_Pinned )
136  m_pinnedLibs.push_back( child->m_LibId.GetLibNickname() );
137  }
138 
139  m_parent->Kiway().Prj().ConfigSave( Kiface().KifaceSearch(), m_parent->GetName(),
141 }
142 
143 
145 {
146  m_filter = aFilter;
147 }
148 
149 
151 {
152  m_show_units = aShow;
153 }
154 
155 
156 void LIB_TREE_MODEL_ADAPTER::SetPreselectNode( LIB_ID const& aLibId, int aUnit )
157 {
158  m_preselect_lib_id = aLibId;
159  m_preselect_unit = aUnit;
160 }
161 
162 
164  wxString const& aDesc )
165 {
166  LIB_TREE_NODE_LIB& lib_node = m_tree.AddLib( aNodeName, aDesc );
167 
168  lib_node.m_Pinned = m_pinnedLibs.Index( lib_node.m_LibId.GetLibNickname() ) != wxNOT_FOUND;
169 
170  return lib_node;
171 }
172 
173 
174 void LIB_TREE_MODEL_ADAPTER::DoAddLibrary( wxString const& aNodeName, wxString const& aDesc,
175  std::vector<LIB_TREE_ITEM*> const& aItemList,
176  bool presorted )
177 {
178  LIB_TREE_NODE_LIB& lib_node = DoAddLibraryNode( aNodeName, aDesc );
179 
180  for( LIB_TREE_ITEM* item: aItemList )
181  lib_node.AddItem( item );
182 
183  lib_node.AssignIntrinsicRanks( presorted );
184 }
185 
186 
187 void LIB_TREE_MODEL_ADAPTER::UpdateSearchString( wxString const& aSearch )
188 {
189  m_tree.ResetScore();
190 
191  for( auto& child: m_tree.m_Children )
192  {
193  if( child->m_Pinned )
194  child->m_Score *= 2;
195  }
196 
197  wxStringTokenizer tokenizer( aSearch );
198 
199  while( tokenizer.HasMoreTokens() )
200  {
201  const wxString term = tokenizer.GetNextToken().Lower();
202  EDA_COMBINED_MATCHER matcher( term );
203 
204  m_tree.UpdateScore( matcher );
205  }
206 
207  m_tree.SortNodes();
208 
209  {
210  wxWindowUpdateLocker updateLock( m_widget );
211 
212  Freeze();
213  // Even with the updateLock, wxWidgets sometimes ties its knickers in
214  // a knot when trying to run a wxdataview_selection_changed_callback()
215  // on a row that has been deleted.
216  // https://bugs.launchpad.net/kicad/+bug/1756255
217  m_widget->UnselectAll();
218 
219  Cleared();
220  Thaw();
221 
222  // This was fixed in wxWidgets 3.0.5 and 3.1.3.
223 #if defined( __WXGTK__ ) && ( (wxVERSION_NUMBER < 030005 ) || \
224  ( ( wxVERSION_NUMBER >= 030100 ) && ( wxVERSION_NUMBER < 030103 ) ) )
225  // The fastest method to update wxDataViewCtrl is to rebuild from
226  // scratch by calling Cleared(). Linux requires to reassociate model to
227  // display data, but Windows will create multiple associations.
228  // On MacOS, this crashes kicad. See https://gitlab.com/kicad/code/kicad/issues/3666
229  // and https://gitlab.com/kicad/code/kicad/issues/3653
230  AttachTo( m_widget );
231 #endif
232  }
233 
234  LIB_TREE_NODE* bestMatch = ShowResults();
235 
236  if( !bestMatch )
237  bestMatch = ShowPreselect();
238 
239  if( !bestMatch )
240  bestMatch = ShowSingleLibrary();
241 
242  if( bestMatch )
243  {
244  auto item = wxDataViewItem( bestMatch );
245  m_widget->Select( item );
246 
247  // Make sure the *parent* item is visible. The selected item is the
248  // first (shown) child of the parent. So it's always right below the parent,
249  // and this way the user can also see what library the selected part belongs to,
250  // without having a case where the selection is off the screen (unless the
251  // window is a single row high, which is unlikely)
252  //
253  // This also happens to circumvent https://bugs.launchpad.net/kicad/+bug/1804400
254  // which appears to be a GTK+3 bug.
255  {
256  wxDataViewItem parent = GetParent( item );
257 
258  if( parent.IsOk() )
259  item = parent;
260  }
261 
262  m_widget->EnsureVisible( item );
263  }
264 }
265 
266 
267 void LIB_TREE_MODEL_ADAPTER::AttachTo( wxDataViewCtrl* aDataViewCtrl )
268 {
269  wxString partHead = _( "Item" );
270  wxString descHead = _( "Description" );
271 
272  // The extent of the text doesn't take into account the space on either side
273  // in the header, so artificially pad it by M
274  wxSize partHeadMinWidth = GetTextSize( partHead + "M", aDataViewCtrl );
275 
276  if( aDataViewCtrl->GetColumnCount() > 0 )
277  {
278  int partWidth = aDataViewCtrl->GetColumn( PART_COL )->GetWidth();
279  int descWidth = aDataViewCtrl->GetColumn( DESC_COL )->GetWidth();
280 
281  // Only use the widths read back if they are non-zero.
282  // GTK returns the displayed width of the column, which is not calculated immediately
283  // this leads to cases of 0 column width if the user types too fast in the filter
284  if( descWidth > 0 )
285  {
286  m_colWidths[PART_COL] = partWidth;
287  m_colWidths[DESC_COL] = descWidth;
288  }
289  }
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  // Ensure the part column is wider than the smallest allowable width
302  if( m_colWidths[PART_COL] < partHeadMinWidth.x )
303  {
304  m_colWidths[PART_COL] = partHeadMinWidth.x;
305  m_col_part->SetWidth( partHeadMinWidth.x );
306  }
307 
308  m_col_part->SetMinWidth( partHeadMinWidth.x );
309 }
310 
311 
312 LIB_ID LIB_TREE_MODEL_ADAPTER::GetAliasFor( const wxDataViewItem& aSelection ) const
313 {
314  const LIB_TREE_NODE* node = ToNode( aSelection );
315 
316  LIB_ID emptyId;
317 
318  if( !node )
319  return emptyId;
320 
321  return node->m_LibId;
322 }
323 
324 
325 int LIB_TREE_MODEL_ADAPTER::GetUnitFor( const wxDataViewItem& aSelection ) const
326 {
327  const LIB_TREE_NODE* node = ToNode( aSelection );
328  return node ? node->m_Unit : 0;
329 }
330 
331 
332 LIB_TREE_NODE::TYPE LIB_TREE_MODEL_ADAPTER::GetTypeFor( const wxDataViewItem& aSelection ) const
333 {
334  const LIB_TREE_NODE* node = ToNode( aSelection );
335  return node ? node->m_Type : LIB_TREE_NODE::INVALID;
336 }
337 
338 
339 LIB_TREE_NODE* LIB_TREE_MODEL_ADAPTER::GetTreeNodeFor( const wxDataViewItem& aSelection ) const
340 {
341  return ToNode( aSelection );
342 }
343 
344 
346 {
347  int n = 0;
348 
349  for( const std::unique_ptr<LIB_TREE_NODE>& lib: m_tree.m_Children )
350  n += lib->m_Children.size();
351 
352  return n;
353 }
354 
355 
356 wxDataViewItem LIB_TREE_MODEL_ADAPTER::FindItem( const LIB_ID& aLibId )
357 {
358  for( auto& lib: m_tree.m_Children )
359  {
360  if( lib->m_Name != aLibId.GetLibNickname() )
361  continue;
362 
363  // if part name is not specified, return the library node
364  if( aLibId.GetLibItemName() == "" )
365  return ToItem( lib.get() );
366 
367  for( auto& alias: lib->m_Children )
368  {
369  if( alias->m_Name == aLibId.GetLibItemName() )
370  return ToItem( alias.get() );
371  }
372 
373  break; // could not find the part in the requested library
374  }
375 
376  return wxDataViewItem();
377 }
378 
379 
380 unsigned int LIB_TREE_MODEL_ADAPTER::GetChildren( wxDataViewItem const& aItem,
381  wxDataViewItemArray& aChildren ) const
382 {
383  auto node = ( aItem.IsOk() ? ToNode( aItem ) : &m_tree );
384 
385  if( node->m_Type != LIB_TREE_NODE::TYPE::LIBID
386  || ( m_show_units && node->m_Type == LIB_TREE_NODE::TYPE::LIBID ) )
387  return IntoArray( *node, aChildren );
388  else
389  return 0;
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  m_col_part->SetWidth( m_col_part->GetWidth() + walk );
401  m_col_desc->SetWidth( m_col_desc->GetWidth() - walk );
402  walk = -walk;
403 }
404 
405 
406 bool LIB_TREE_MODEL_ADAPTER::HasContainerColumns( wxDataViewItem const& aItem ) const
407 {
408  return IsContainer( aItem );
409 }
410 
411 
412 bool LIB_TREE_MODEL_ADAPTER::IsContainer( wxDataViewItem const& aItem ) const
413 {
414  auto node = ToNode( aItem );
415  return node ? node->m_Children.size() : true;
416 }
417 
418 
419 wxDataViewItem LIB_TREE_MODEL_ADAPTER::GetParent( wxDataViewItem const& aItem ) const
420 {
421  if( m_freeze )
422  return ToItem( nullptr );
423 
424  auto node = ToNode( aItem );
425  auto parent = node ? node->m_Parent : nullptr;
426 
427  // wxDataViewModel has no root node, but rather top-level elements have
428  // an invalid (null) parent.
429  if( !node || !parent || parent->m_Type == LIB_TREE_NODE::TYPE::ROOT )
430  return ToItem( nullptr );
431  else
432  return ToItem( parent );
433 }
434 
435 
436 void LIB_TREE_MODEL_ADAPTER::GetValue( wxVariant& aVariant,
437  wxDataViewItem const& aItem,
438  unsigned int aCol ) const
439 {
440  if( IsFrozen() )
441  {
442  aVariant = wxEmptyString;
443  return;
444  }
445 
446  auto node = ToNode( aItem );
447  wxASSERT( node );
448 
449  switch( aCol )
450  {
451  default: // column == -1 is used for default Compare function
452  case 0:
453  aVariant = node->m_Name;
454  break;
455  case 1:
456  aVariant = node->m_Desc;
457  break;
458  }
459 }
460 
461 
462 bool LIB_TREE_MODEL_ADAPTER::GetAttr( wxDataViewItem const& aItem,
463  unsigned int aCol,
464  wxDataViewItemAttr& aAttr ) const
465 {
466  if( IsFrozen() )
467  return false;
468 
469  auto node = ToNode( aItem );
470  wxASSERT( node );
471 
472  if( node->m_Type != LIB_TREE_NODE::LIBID )
473  {
474  // Currently only aliases are formatted at all
475  return false;
476  }
477 
478  if( !node->m_IsRoot && aCol == 0 )
479  {
480  // Names of non-root aliases are italicized
481  aAttr.SetItalic( true );
482  return true;
483  }
484  else
485  {
486  return false;
487  }
488 }
489 
490 
492  std::function<bool( LIB_TREE_NODE const* )> aFunc,
493  LIB_TREE_NODE** aHighScore )
494 {
495  for( auto& node: aNode.m_Children )
496  {
497  if( aFunc( &*node ) )
498  {
499  auto item = wxDataViewItem( &*node );
500  m_widget->ExpandAncestors( item );
501 
502  if( !(*aHighScore) || node->m_Score > (*aHighScore)->m_Score )
503  (*aHighScore) = &*node;
504  }
505 
506  FindAndExpand( *node, aFunc, aHighScore );
507  }
508 }
509 
510 
512 {
513  LIB_TREE_NODE* highScore = nullptr;
514 
516  []( LIB_TREE_NODE const* n )
517  {
518  // return leaf nodes with some level of matching
519  return n->m_Type == LIB_TREE_NODE::TYPE::LIBID && n->m_Score > 1;
520  },
521  &highScore );
522 
523  return highScore;
524 }
525 
526 
528 {
529  LIB_TREE_NODE* highScore = nullptr;
530 
531  if( !m_preselect_lib_id.IsValid() )
532  return highScore;
533 
535  [&]( LIB_TREE_NODE const* n )
536  {
537  if( n->m_Type == LIB_TREE_NODE::LIBID && ( n->m_Children.empty() || !m_preselect_unit ) )
538  return m_preselect_lib_id == n->m_LibId;
539  else if( n->m_Type == LIB_TREE_NODE::UNIT && m_preselect_unit )
541  else
542  return false;
543  },
544  &highScore );
545 
546  return highScore;
547 }
548 
549 
551 {
552  LIB_TREE_NODE* highScore = nullptr;
553 
555  []( LIB_TREE_NODE const* n )
556  {
557  return n->m_Type == LIB_TREE_NODE::TYPE::LIBID &&
558  n->m_Parent->m_Parent->m_Children.size() == 1;
559  },
560  &highScore );
561 
562  return highScore;
563 }
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:114
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
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.
std::vector< PARAM_CFG * > & GetProjectFileParameters()
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:171
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
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:260
static unsigned int IntoArray(LIB_TREE_NODE const &aNode, wxDataViewItemArray &aChildren)
Convert CMP_TREE_NODE's children to wxDataViewItemArray.
VTBL_ENTRY bool ConfigLoad(const SEARCH_STACK &aSearchS, const wxString &aGroupName, const std::vector< PARAM_CFG * > &aParams, const wxString &aForeignConfigFileName=wxEmptyString)
Function ConfigLoad reads a subset of parameters from the "project" file.
Definition: project.cpp:407
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 ...
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.
VTBL_ENTRY void ConfigSave(const SEARCH_STACK &aSList, const wxString &aGroupName, const std::vector< PARAM_CFG * > &aParams, const wxString &aFileName=wxEmptyString)
Function ConfigSave saves the current "project" parameters into the wxConfigBase* derivative.
Definition: project.cpp:362
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)
std::vector< PARAM_CFG * > m_projectFileParams
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
#define PINNED_ITEMS_KEY
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.
LIB_TREE_MODEL_ADAPTER(EDA_BASE_FRAME *aParent)
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.