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-2019 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_pattern_match.h>
23 #include <kiface_i.h>
24 #include <lib_tree_model_adapter.h>
25 #include <wx/tokenzr.h>
26 #include <wx/wupdlock.h>
27 
28 
29 #define LIST_COLUMN_WIDTH_KEY wxT( "SelectorColumnWidth" )
30 
31 static const int kDataViewIndent = 20;
32 
33 
37 wxDataViewItem LIB_TREE_MODEL_ADAPTER::ToItem( LIB_TREE_NODE const* aNode )
38 {
39  return wxDataViewItem( const_cast<void*>( static_cast<void const*>( aNode ) ) );
40 }
41 
42 
46 LIB_TREE_NODE const* LIB_TREE_MODEL_ADAPTER::ToNode( wxDataViewItem aItem )
47 {
48  return static_cast<LIB_TREE_NODE const*>( aItem.GetID() );
49 }
50 
51 
56  wxDataViewItemArray& aChildren )
57 {
58  unsigned int n = 0;
59 
60  for( auto const& child: aNode.Children )
61  {
62  if( child->Score > 0 )
63  {
64  aChildren.Add( ToItem( &*child ) );
65  ++n;
66  }
67  }
68 
69  return n;
70 }
71 
72 
74  :m_filter( CMP_FILTER_NONE ),
75  m_show_units( true ),
76  m_preselect_unit( 0 ),
77  m_freeze( 0 ),
78  m_col_part( nullptr ),
79  m_col_desc( nullptr ),
80  m_widget( nullptr )
81 {
82  // Default column widths
83  m_colWidths[PART_COL] = 360;
84  m_colWidths[DESC_COL] = 2000;
85 
87  m_configPrefix = typeid( this ).name();
88 
89  // Read the column width from the config
90  int colWidth = 0;
91 
92  if( m_config->Read( m_configPrefix + LIST_COLUMN_WIDTH_KEY, &colWidth ) )
93  m_colWidths[PART_COL] = colWidth;
94 }
95 
96 
98 {}
99 
100 
102 {
103  if( m_widget )
104  {
105  int colWidth = m_widget->GetColumn( PART_COL )->GetWidth();
106  m_config->Write( m_configPrefix + LIST_COLUMN_WIDTH_KEY, colWidth );
107  }
108  else
109  {
110  wxLogDebug( "Error saving column size, tree view doesn't exist" );
111  }
112 }
113 
114 
116 {
117  m_filter = aFilter;
118 }
119 
120 
122 {
123  m_show_units = aShow;
124 }
125 
126 
127 void LIB_TREE_MODEL_ADAPTER::SetPreselectNode( LIB_ID const& aLibId, int aUnit )
128 {
129  m_preselect_lib_id = aLibId;
130  m_preselect_unit = aUnit;
131 }
132 
133 
134 void LIB_TREE_MODEL_ADAPTER::DoAddLibrary( wxString const& aNodeName, wxString const& aDesc,
135  std::vector<LIB_TREE_ITEM*> const& aItemList,
136  bool presorted )
137 {
138  LIB_TREE_NODE_LIB& lib_node = m_tree.AddLib( aNodeName, aDesc );
139 
140  for( LIB_TREE_ITEM* item: aItemList )
141  lib_node.AddItem( item );
142 
143  lib_node.AssignIntrinsicRanks( presorted );
144 }
145 
146 
147 void LIB_TREE_MODEL_ADAPTER::UpdateSearchString( wxString const& aSearch )
148 {
149  m_tree.ResetScore();
150 
151  wxStringTokenizer tokenizer( aSearch );
152 
153  while( tokenizer.HasMoreTokens() )
154  {
155  const wxString term = tokenizer.GetNextToken().Lower();
156  EDA_COMBINED_MATCHER matcher( term );
157 
158  m_tree.UpdateScore( matcher );
159  }
160 
161  m_tree.SortNodes();
162 
163  {
164  wxWindowUpdateLocker updateLock( m_widget );
165 
166  // Even with the updateLock, wxWidgets sometimes ties its knickers in
167  // a knot when trying to run a wxdataview_selection_changed_callback()
168  // on a row that has been deleted.
169  // https://bugs.launchpad.net/kicad/+bug/1756255
170  m_widget->UnselectAll();
171 
172  Cleared();
173 #ifndef __WINDOWS__
174  // The fastest method to update wxDataViewCtrl is to rebuild from
175  // scratch by calling Cleared(). Linux requires to reassociate model to
176  // display data, but Windows will create multiple associations.
177  AttachTo( m_widget );
178 #endif
179  }
180 
181  LIB_TREE_NODE* bestMatch = ShowResults();
182 
183  if( !bestMatch )
184  bestMatch = ShowPreselect();
185 
186  if( !bestMatch )
187  bestMatch = ShowSingleLibrary();
188 
189  if( bestMatch )
190  {
191  auto item = wxDataViewItem( bestMatch );
192  m_widget->Select( item );
193 
194  // Make sure the *parent* item is visible. The selected item is the
195  // first (shown) child of the parent. So it's always right below the parent,
196  // and this way the user can also see what library the selected part belongs to,
197  // without having a case where the selection is off the screen (unless the
198  // window is a single row high, which is unlikely)
199  //
200  // This also happens to circumvent https://bugs.launchpad.net/kicad/+bug/1804400
201  // which appears to be a GTK+3 bug.
202  {
203  wxDataViewItem parent = GetParent( item );
204 
205  if( parent.IsOk() )
206  item = parent;
207  }
208 
209  m_widget->EnsureVisible( item );
210  }
211 }
212 
213 
214 void LIB_TREE_MODEL_ADAPTER::AttachTo( wxDataViewCtrl* aDataViewCtrl )
215 {
216  wxString partHead = _( "Item" );
217  wxString descHead = _( "Description" );
218 
219  // The extent of the text doesn't take into account the space on either side
220  // in the header, so artificially pad it by M
221  wxSize partHeadMinWidth = GetTextSize( partHead + "M", aDataViewCtrl );
222 
223  if( aDataViewCtrl->GetColumnCount() > 0 )
224  {
225  int partWidth = aDataViewCtrl->GetColumn( PART_COL )->GetWidth();
226  int descWidth = aDataViewCtrl->GetColumn( DESC_COL )->GetWidth();
227 
228  // Only use the widths read back if they are non-zero.
229  // GTK returns the displayed width of the column, which is not calculated immediately
230  // this leads to cases of 0 column width if the user types too fast in the filter
231  if( descWidth > 0 )
232  {
233  m_colWidths[PART_COL] = partWidth;
234  m_colWidths[DESC_COL] = descWidth;
235  }
236  }
237 
238  m_widget = aDataViewCtrl;
239  aDataViewCtrl->SetIndent( kDataViewIndent );
240  aDataViewCtrl->AssociateModel( this );
241  aDataViewCtrl->ClearColumns();
242 
243  m_col_part = aDataViewCtrl->AppendTextColumn( partHead, PART_COL, wxDATAVIEW_CELL_INERT,
245  m_col_desc = aDataViewCtrl->AppendTextColumn( descHead, DESC_COL, wxDATAVIEW_CELL_INERT,
247 
248  // Ensure the part column is wider than the smallest allowable width
249  if( m_colWidths[PART_COL] < partHeadMinWidth.x )
250  {
251  m_colWidths[PART_COL] = partHeadMinWidth.x;
252  m_col_part->SetWidth( partHeadMinWidth.x );
253  }
254 
255  m_col_part->SetMinWidth( partHeadMinWidth.x );
256 }
257 
258 
259 LIB_ID LIB_TREE_MODEL_ADAPTER::GetAliasFor( const wxDataViewItem& aSelection ) const
260 {
261  const LIB_TREE_NODE* node = ToNode( aSelection );
262 
263  LIB_ID emptyId;
264 
265  if( !node )
266  return emptyId;
267 
268  return node->LibId;
269 }
270 
271 
272 int LIB_TREE_MODEL_ADAPTER::GetUnitFor( const wxDataViewItem& aSelection ) const
273 {
274  const LIB_TREE_NODE* node = ToNode( aSelection );
275  return node ? node->Unit : 0;
276 }
277 
278 
279 LIB_TREE_NODE::TYPE LIB_TREE_MODEL_ADAPTER::GetTypeFor( const wxDataViewItem& aSelection ) const
280 {
281  const LIB_TREE_NODE* node = ToNode( aSelection );
282  return node ? node->Type : LIB_TREE_NODE::INVALID;
283 }
284 
285 
287 {
288  int n = 0;
289 
290  for( const std::unique_ptr<LIB_TREE_NODE>& lib: m_tree.Children )
291  n += lib->Children.size();
292 
293  return n;
294 }
295 
296 
297 wxDataViewItem LIB_TREE_MODEL_ADAPTER::FindItem( const LIB_ID& aLibId )
298 {
299  for( auto& lib: m_tree.Children )
300  {
301  if( lib->Name != aLibId.GetLibNickname() )
302  continue;
303 
304  // if part name is not specified, return the library node
305  if( aLibId.GetLibItemName() == "" )
306  return ToItem( lib.get() );
307 
308  for( auto& alias: lib->Children )
309  {
310  if( alias->Name == aLibId.GetLibItemName() )
311  return ToItem( alias.get() );
312  }
313 
314  break; // could not find the part in the requested library
315  }
316 
317  return wxDataViewItem();
318 }
319 
320 
321 unsigned int LIB_TREE_MODEL_ADAPTER::GetChildren( wxDataViewItem const& aItem,
322  wxDataViewItemArray& aChildren ) const
323 {
324  auto node = ( aItem.IsOk() ? ToNode( aItem ) : &m_tree );
325 
326  if( node->Type != LIB_TREE_NODE::TYPE::LIBID
327  || ( m_show_units && node->Type == LIB_TREE_NODE::TYPE::LIBID ) )
328  return IntoArray( *node, aChildren );
329  else
330  return 0;
331 }
332 
333 
334 bool LIB_TREE_MODEL_ADAPTER::HasContainerColumns( wxDataViewItem const& aItem ) const
335 {
336  return IsContainer( aItem );
337 }
338 
339 
340 bool LIB_TREE_MODEL_ADAPTER::IsContainer( wxDataViewItem const& aItem ) const
341 {
342  auto node = ToNode( aItem );
343  return node ? node->Children.size() : true;
344 }
345 
346 
347 wxDataViewItem LIB_TREE_MODEL_ADAPTER::GetParent( wxDataViewItem const& aItem ) const
348 {
349  auto node = ToNode( aItem );
350  auto parent = node ? node->Parent : nullptr;
351 
352  // wxDataViewModel has no root node, but rather top-level elements have
353  // an invalid (null) parent.
354  if( !node || !parent || parent->Type == LIB_TREE_NODE::TYPE::ROOT )
355  return ToItem( nullptr );
356  else
357  return ToItem( parent );
358 }
359 
360 
361 void LIB_TREE_MODEL_ADAPTER::GetValue( wxVariant& aVariant,
362  wxDataViewItem const& aItem,
363  unsigned int aCol ) const
364 {
365  if( IsFrozen() )
366  {
367  aVariant = wxEmptyString;
368  return;
369  }
370 
371  auto node = ToNode( aItem );
372  wxASSERT( node );
373 
374  switch( aCol )
375  {
376  default: // column == -1 is used for default Compare function
377  case 0:
378  aVariant = node->Name;
379  break;
380  case 1:
381  aVariant = node->Desc;
382  break;
383  }
384 }
385 
386 
387 bool LIB_TREE_MODEL_ADAPTER::GetAttr( wxDataViewItem const& aItem,
388  unsigned int aCol,
389  wxDataViewItemAttr& aAttr ) const
390 {
391  if( IsFrozen() )
392  return false;
393 
394  auto node = ToNode( aItem );
395  wxASSERT( node );
396 
397  if( node->Type != LIB_TREE_NODE::LIBID )
398  {
399  // Currently only aliases are formatted at all
400  return false;
401  }
402 
403  if( !node->IsRoot && aCol == 0 )
404  {
405  // Names of non-root aliases are italicized
406  aAttr.SetItalic( true );
407  return true;
408  }
409  else
410  {
411  return false;
412  }
413 }
414 
415 
417  std::function<bool( LIB_TREE_NODE const* )> aFunc,
418  LIB_TREE_NODE** aHighScore )
419 {
420  for( auto& node: aNode.Children )
421  {
422  if( aFunc( &*node ) )
423  {
424  auto item = wxDataViewItem( &*node );
425  m_widget->ExpandAncestors( item );
426 
427  if( !(*aHighScore) || node->Score > (*aHighScore)->Score )
428  (*aHighScore) = &*node;
429  }
430 
431  FindAndExpand( *node, aFunc, aHighScore );
432  }
433 }
434 
435 
437 {
438  LIB_TREE_NODE* highScore = nullptr;
439 
441  []( LIB_TREE_NODE const* n )
442  {
443  // return leaf nodes with some level of matching
444  return n->Type == LIB_TREE_NODE::TYPE::LIBID && n->Score > 1;
445  },
446  &highScore );
447 
448  return highScore;
449 }
450 
451 
453 {
454  LIB_TREE_NODE* highScore = nullptr;
455 
456  if( !m_preselect_lib_id.IsValid() )
457  return highScore;
458 
460  [&]( LIB_TREE_NODE const* n )
461  {
462  if( n->Type == LIB_TREE_NODE::LIBID && ( n->Children.empty() || !m_preselect_unit ) )
463  return m_preselect_lib_id == n->LibId;
464  else if( n->Type == LIB_TREE_NODE::UNIT && m_preselect_unit )
465  return m_preselect_lib_id == n->Parent->LibId && m_preselect_unit == n->Unit;
466  else
467  return false;
468  },
469  &highScore );
470 
471  return highScore;
472 }
473 
474 
476 {
477  LIB_TREE_NODE* highScore = nullptr;
478 
480  []( LIB_TREE_NODE const* n )
481  {
482  return n->Type == LIB_TREE_NODE::TYPE::LIBID &&
483  n->Parent->Parent->Children.size() == 1;
484  },
485  &highScore );
486 
487  return highScore;
488 }
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.
CMP_FILTER_TYPE
This enum allows a selective filtering of components to list.
PTR_VECTOR Children
List of child nodes.
enum TYPE Type
Node type.
A mix-in to provide polymorphism between items stored in libraries (symbols, aliases and footprints).
Definition: lib_tree_item.h:39
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.
wxConfigBase * KifaceSettings() const
Definition: kiface_i.h:103
LIB_TREE_NODE * ShowResults()
Find and expand successful search results.
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
LIB_TREE_NODE * Parent
Parent node or null.
int Score
The score of an item resulting from the search algorithm.
KIFACE_I & Kiface()
Global KIFACE_I "get" accessor.
Definition: kicad.cpp:52
static wxDataViewItem ToItem(LIB_TREE_NODE const *aNode)
Convert CMP_TREE_NODE -> wxDataViewItem.
Abstract pattern-matching tool and implementations.
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:115
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 (f(CMP_TREE_NODE con...
Node type: library.
void SetFilter(CMP_FILTER_TYPE aFilter)
Set the component filter type.
#define LIST_COLUMN_WIDTH_KEY
static LIB_TREE_NODE const * 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.
LIB_ID LibId
LIB_ID determined by the parent library nickname and alias name.
void ResetScore()
Initialize score to kLowestDefaultScore, recursively.
Model class in the component selector Model-View-Adapter (mediated MVC) architecture.
#define _(s)
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.
const char * name
Definition: DXF_plotter.cpp:61
int Unit
Actual unit, or zero.
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.
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.