KiCad PCB EDA Suite
cmp_tree_model_adapter_base.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-2017 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 
23 
24 #include <eda_pattern_match.h>
25 
26 #include <wx/progdlg.h>
27 #include <wx/tokenzr.h>
28 #include <wx/wupdlock.h>
29 
30 
31 CMP_TREE_MODEL_ADAPTER_BASE::WIDTH_CACHE CMP_TREE_MODEL_ADAPTER_BASE::m_width_cache;
32 
34 
35 #define PROGRESS_INTERVAL_MILLIS 66
36 
37 static const int kDataViewIndent = 20;
38 
39 
43 wxDataViewItem CMP_TREE_MODEL_ADAPTER_BASE::ToItem( CMP_TREE_NODE const* aNode )
44 {
45  return wxDataViewItem( const_cast<void*>( static_cast<void const*>( aNode ) ) );
46 }
47 
48 
52 CMP_TREE_NODE const* CMP_TREE_MODEL_ADAPTER_BASE::ToNode( wxDataViewItem aItem )
53 {
54  return static_cast<CMP_TREE_NODE const*>( aItem.GetID() );
55 }
56 
57 
62  CMP_TREE_NODE const& aNode, wxDataViewItemArray& aChildren )
63 {
64  unsigned int n = 0;
65 
66  for( auto const& child: aNode.Children )
67  {
68  if( child->Score > 0 )
69  {
70  aChildren.Add( ToItem( &*child ) );
71  ++n;
72  }
73  }
74 
75  return n;
76 }
77 
78 
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 {}
88 
89 
91 {}
92 
93 
95 {
96  m_filter = aFilter;
97 }
98 
99 
101 {
102  m_show_units = aShow;
103 }
104 
105 
107 {
108  m_preselect_lib_id = aLibId;
109  m_preselect_unit = aUnit;
110 }
111 
112 
114  const std::vector<wxString>& aNicknames, wxWindow* aParent )
115 {
116  wxProgressDialog* prg = nullptr;
117  wxLongLong nextUpdate = wxGetUTCTimeMillis() + (PROGRESS_INTERVAL_MILLIS / 2);
118 
119  if( m_show_progress )
120  prg = new wxProgressDialog( _( "Loading Symbol Libraries" ),
121  wxEmptyString,
122  aNicknames.size(),
123  aParent );
124 
125  unsigned int ii = 0;
126 
127  for( const auto& nickname : aNicknames )
128  {
129  if( prg && wxGetUTCTimeMillis() > nextUpdate )
130  {
131  prg->Update( ii, wxString::Format( _( "Loading library \"%s\"" ), nickname ) );
132  nextUpdate = wxGetUTCTimeMillis() + PROGRESS_INTERVAL_MILLIS;
133  }
134 
135  AddLibrary( nickname );
136  ii++;
137  }
138 
139  if( prg )
140  {
141  prg->Destroy();
142  m_show_progress = false;
143  }
144 }
145 
146 
147 void CMP_TREE_MODEL_ADAPTER_BASE::AddAliasList( wxString const& aNodeName, wxString const& aDesc,
148  std::vector<LIB_ALIAS*> const& aAliasList )
149 {
150  auto& lib_node = m_tree.AddLib( aNodeName, aDesc );
151 
152  for( auto a: aAliasList )
153  {
154  lib_node.AddAlias( a );
155  }
156 
157  lib_node.AssignIntrinsicRanks();
159 }
160 
161 
162 void CMP_TREE_MODEL_ADAPTER_BASE::UpdateSearchString( wxString const& aSearch )
163 {
164  m_tree.ResetScore();
165 
166  wxStringTokenizer tokenizer( aSearch );
167 
168  while( tokenizer.HasMoreTokens() )
169  {
170  const wxString term = tokenizer.GetNextToken().Lower();
171  EDA_COMBINED_MATCHER matcher( term );
172 
173  m_tree.UpdateScore( matcher );
174  }
175 
176  m_tree.SortNodes();
177 
178  {
179  wxWindowUpdateLocker updateLock( m_widget );
180 
181  // Even with the updateLock, wxWidgets sometimes ties its knickers in
182  // a knot when trying to run a wxdataview_selection_changed_callback()
183  // on a row that has been deleted.
184  // https://bugs.launchpad.net/kicad/+bug/1756255
185  m_widget->UnselectAll();
186 
187  Cleared();
188 #ifndef __WINDOWS__
189  // The fastest method to update wxDataViewCtrl is to rebuild from
190  // scratch by calling Cleared(). Linux requires to reassociate model to
191  // display data, but Windows will create multiple associations.
192  AttachTo( m_widget );
193 #endif
194  }
195 
196  CMP_TREE_NODE* bestMatch = ShowResults();
197 
198  if( !bestMatch )
199  bestMatch = ShowPreselect();
200 
201  if( !bestMatch )
202  bestMatch = ShowSingleLibrary();
203 
204  if( bestMatch )
205  {
206  auto item = wxDataViewItem( bestMatch );
207  m_widget->Select( item );
208  m_widget->EnsureVisible( item );
209  }
210 }
211 
212 
213 void CMP_TREE_MODEL_ADAPTER_BASE::AttachTo( wxDataViewCtrl* aDataViewCtrl )
214 {
215  m_widget = aDataViewCtrl;
216  aDataViewCtrl->SetIndent( kDataViewIndent );
217  aDataViewCtrl->AssociateModel( this );
218  aDataViewCtrl->ClearColumns();
219 
220  wxString part_head = _( "Symbol" );
221  wxString desc_head = _( "Desc" );
222 
223  m_col_part = aDataViewCtrl->AppendTextColumn( part_head, 0, wxDATAVIEW_CELL_INERT,
224  ColWidth( m_tree, 0, part_head ) );
225  m_col_desc = aDataViewCtrl->AppendTextColumn( desc_head, 1, wxDATAVIEW_CELL_INERT,
226  ColWidth( m_tree, 1, desc_head ) );
227 }
228 
229 
230 LIB_ID CMP_TREE_MODEL_ADAPTER_BASE::GetAliasFor( const wxDataViewItem& aSelection ) const
231 {
232  auto node = ToNode( aSelection );
233 
234  LIB_ID emptyId;
235 
236  if( !node )
237  return emptyId;
238 
239  return node->LibId;
240 }
241 
242 
243 int CMP_TREE_MODEL_ADAPTER_BASE::GetUnitFor( const wxDataViewItem& aSelection ) const
244 {
245  auto node = ToNode( aSelection );
246  return node ? node->Unit : 0;
247 }
248 
249 
250 CMP_TREE_NODE::TYPE CMP_TREE_MODEL_ADAPTER_BASE::GetTypeFor( const wxDataViewItem& aSelection ) const
251 {
252  auto node = ToNode( aSelection );
253  return node ? node->Type : CMP_TREE_NODE::INVALID;
254 }
255 
256 
258 {
259  int n = 0;
260 
261  for( auto& lib: m_tree.Children )
262  {
263  for( auto& alias: lib->Children )
264  {
265  (void) alias;
266  ++n;
267  }
268  }
269 
270  return n;
271 }
272 
273 
274 wxDataViewItem CMP_TREE_MODEL_ADAPTER_BASE::FindItem( const LIB_ID& aLibId )
275 {
276  for( auto& lib: m_tree.Children )
277  {
278  if( lib->Name != aLibId.GetLibNickname() )
279  continue;
280 
281  // if part name is not specified, return the library node
282  if( aLibId.GetLibItemName() == "" )
283  return ToItem( lib.get() );
284 
285  for( auto& alias: lib->Children )
286  {
287  if( alias->Name == aLibId.GetLibItemName() )
288  return ToItem( alias.get() );
289  }
290 
291  break; // could not find the part in the requested library
292  }
293 
294  return wxDataViewItem();
295 }
296 
297 
299  wxDataViewItem const& aItem,
300  wxDataViewItemArray& aChildren ) const
301 {
302  auto node = ( aItem.IsOk() ? ToNode( aItem ) : &m_tree );
303 
304  if( node->Type != CMP_TREE_NODE::TYPE::LIBID
305  || ( m_show_units && node->Type == CMP_TREE_NODE::TYPE::LIBID ) )
306  return IntoArray( *node, aChildren );
307  else
308  return 0;
309 }
310 
311 
312 bool CMP_TREE_MODEL_ADAPTER_BASE::HasContainerColumns( wxDataViewItem const& aItem ) const
313 {
314  return IsContainer( aItem );
315 }
316 
317 
318 bool CMP_TREE_MODEL_ADAPTER_BASE::IsContainer( wxDataViewItem const& aItem ) const
319 {
320  auto node = ToNode( aItem );
321  return node ? node->Children.size() : true;
322 }
323 
324 
325 wxDataViewItem CMP_TREE_MODEL_ADAPTER_BASE::GetParent( wxDataViewItem const& aItem ) const
326 {
327  auto node = ToNode( aItem );
328  auto parent = node ? node->Parent : nullptr;
329 
330  // wxDataViewModel has no root node, but rather top-level elements have
331  // an invalid (null) parent.
332  if( !node || !parent || parent->Type == CMP_TREE_NODE::TYPE::ROOT )
333  {
334  return ToItem( nullptr );
335  }
336  else
337  {
338  return ToItem( parent );
339  }
340 }
341 
342 
344  wxVariant& aVariant,
345  wxDataViewItem const& aItem,
346  unsigned int aCol ) const
347 {
348  if( IsFrozen() )
349  {
350  aVariant = wxEmptyString;
351  return;
352  }
353 
354  auto node = ToNode( aItem );
355  wxASSERT( node );
356 
357  switch( aCol )
358  {
359  default: // column == -1 is used for default Compare function
360  case 0:
361  aVariant = node->Name;
362  break;
363  case 1:
364  aVariant = node->Desc;
365  break;
366  }
367 }
368 
369 
371  wxDataViewItem const& aItem,
372  unsigned int aCol,
373  wxDataViewItemAttr& aAttr ) const
374 {
375  if( IsFrozen() )
376  return false;
377 
378  auto node = ToNode( aItem );
379  wxASSERT( node );
380 
381  if( node->Type != CMP_TREE_NODE::LIBID )
382  {
383  // Currently only aliases are formatted at all
384  return false;
385  }
386 
387  if( !node->IsRoot && aCol == 0 )
388  {
389  // Names of non-root aliases are italicized
390  aAttr.SetItalic( true );
391  return true;
392  }
393  else
394  {
395  return false;
396  }
397 }
398 
399 
400 int CMP_TREE_MODEL_ADAPTER_BASE::ColWidth( CMP_TREE_NODE& aTree, int aCol, wxString const& aHeading )
401 {
402  const int indent = aCol ? 0 : kDataViewIndent;
403 
404  int min_width = WidthFor( aHeading, aCol );
405  int width = std::max( aTree.Score > 0 ? WidthFor( aTree, aCol ) : 0, min_width );
406 
407  if( aTree.Score > 0 )
408  {
409  for( auto& node: aTree.Children )
410  {
411  width = std::max( width, ColWidth( *node, aCol, aHeading ) + indent );
412  }
413  }
414 
415  return width;
416 }
417 
418 
420 {
421  auto result = m_width_cache.find( aNode.Name );
422 
423  if( result != m_width_cache.end() )
424  {
425  return result->second[aCol];
426  }
427  else
428  {
429  int wname = m_widget->GetTextExtent( aNode.Name ).x + kDataViewIndent;
430  int wdesc = m_widget->GetTextExtent( aNode.Desc ).x;
431 
432  auto& val = m_width_cache[aNode.Name];
433  val.push_back( wname );
434  val.push_back( wdesc );
435  return val[aCol];
436  }
437 }
438 
439 
440 int CMP_TREE_MODEL_ADAPTER_BASE::WidthFor( wxString const& aHeading, int aCol )
441 {
442  static std::vector<int> widths;
443 
444  for( int i = (int) widths.size(); i <= aCol; ++i )
445  {
446  widths.push_back( 0 );
447  }
448 
449  if( widths[aCol] == 0 )
450  {
451  widths[aCol] = m_widget->GetTextExtent( aHeading ).x;
452  }
453 
454  return widths[aCol];
455 }
456 
457 
459  std::function<bool( CMP_TREE_NODE const* )> aFunc,
460  CMP_TREE_NODE** aHighScore )
461 {
462  for( auto& node: aNode.Children )
463  {
464  if( aFunc( &*node ) )
465  {
466  auto item = wxDataViewItem( &*node );
467  m_widget->ExpandAncestors( item );
468 
469  if( !(*aHighScore) || node->Score > (*aHighScore)->Score )
470  (*aHighScore) = &*node;
471  }
472  else
473  {
474  FindAndExpand( *node, aFunc, aHighScore );
475  }
476  }
477 }
478 
479 
481 {
482  CMP_TREE_NODE* highScore = nullptr;
483 
485  []( CMP_TREE_NODE const* n )
486  {
487  return n->Type == CMP_TREE_NODE::TYPE::LIBID && n->Score > 1;
488  },
489  &highScore );
490 
491  return highScore;
492 }
493 
494 
496 {
497  CMP_TREE_NODE* highScore = nullptr;
498 
499  if( !m_preselect_lib_id.IsValid() )
500  return highScore;
501 
503  [&]( CMP_TREE_NODE const* n )
504  {
505  if( n->Type == CMP_TREE_NODE::LIBID && ( n->Children.empty() || !m_preselect_unit ) )
506  return m_preselect_lib_id == n->LibId;
507  else if( n->Type == CMP_TREE_NODE::UNIT && m_preselect_unit )
508  return m_preselect_lib_id == n->Parent->LibId && m_preselect_unit == n->Unit;
509  else
510  return false;
511  },
512  &highScore );
513 
514  return highScore;
515 }
516 
517 
519 {
520  CMP_TREE_NODE* highScore = nullptr;
521 
523  []( CMP_TREE_NODE const* n )
524  {
525  return n->Type == CMP_TREE_NODE::TYPE::LIBID &&
526  n->Parent->Parent->Children.size() == 1;
527  },
528  &highScore );
529 
530  return highScore;
531 }
bool IsValid() const
Definition: lib_id.h:175
static unsigned int IntoArray(CMP_TREE_NODE const &aNode, wxDataViewItemArray &aChildren)
Convert CMP_TREE_NODE&#39;s children to wxDataViewItemArray.
CMP_TREE_NODE::TYPE GetTypeFor(const wxDataViewItem &aSelection) const
Return node type for the given item.
wxString Name
Actual name of the part.
wxString Desc
Description to be displayed.
CMP_TREE_NODE_LIB & AddLib(wxString const &aName, wxString const &aDesc)
Construct an empty library node, add it to the root, and return it.
int GetComponentsCount() const
Return the number of components loaded in the tree.
void ShowUnits(bool aShow)
Whether or not to show units.
virtual bool GetAttr(wxDataViewItem const &aItem, unsigned int aCol, wxDataViewItemAttr &aAttr) const override
Get any formatting for an item.
virtual void UpdateScore(EDA_COMBINED_MATCHER &aMatcher) override
Update the score for this part.
static const int kDataViewIndent
enum TYPE Type
Node type.
CMP_TREE_NODE_LIB_ID & AddAlias(LIB_ALIAS *aAlias)
Construct a new alias node, add it to this library, and return it.
void ResetScore()
Initialize score to kLowestDefaultScore, recursively.
A logical library item identifier and consists of various portions much like a URI.
Definition: lib_id.h:51
virtual void GetValue(wxVariant &aVariant, wxDataViewItem const &aItem, unsigned int aCol) const override
Get the value of an item.
void SortNodes()
Sort child nodes quickly and recursively (IntrinsicRanks must have been set).
LIB_ID LibId
LIB_ID determined by the parent library nickname and alias name.
static CMP_TREE_NODE const * ToNode(wxDataViewItem aItem)
Convert wxDataViewItem -> CMP_TREE_NODE.
Abstract pattern-matching tool and implementations.
void AddLibrariesWithProgress(const std::vector< wxString > &aNicknames, wxWindow *aParent)
Add all the libraries in a SYMBOL_LIB_TABLE to the model, displaying a progress dialog attached to th...
void AssignIntrinsicRanks()
Store intrinsic ranks on all children of this node.
static bool m_show_progress
Flag to only show the symbol library table load progress dialog the first time.
CMP_TREE_NODE * ShowResults()
Find and expand successful search results.
const UTF8 & GetLibItemName() const
Definition: lib_id.h:118
int GetUnitFor(const wxDataViewItem &aSelection) const
Return the unit for the given item.
void UpdateSearchString(wxString const &aSearch)
Set the search string provided by the user.
virtual void AddLibrary(wxString const &aLibNickname)=0
Add all the components and their aliases in this library.
int WidthFor(CMP_TREE_NODE &aNode, int aCol)
Return the width required to display a single row&#39;s aCol text.
virtual unsigned int GetChildren(wxDataViewItem const &aItem, wxDataViewItemArray &aChildren) const override
Populate a list of all the children of an item.
void FindAndExpand(CMP_TREE_NODE &aNode, std::function< bool(CMP_TREE_NODE const *)> aFunc, CMP_TREE_NODE **aHighScore)
Find any results worth highlighting and expand them, according to given criteria (f(CMP_TREE_NODE con...
#define PROGRESS_INTERVAL_MILLIS
CMP_TREE_NODE * ShowPreselect()
Find and expand preselected node.
LIB_ID GetAliasFor(const wxDataViewItem &aSelection) const
Return the alias for the given item.
Model class in the component selector Model-View-Adapter (mediated MVC) architecture.
virtual void AddAliasList(wxString const &aNodeName, wxArrayString const &aAliasNameList)=0
Add the given list of components, by name.
CMP_TREE_NODE * Parent
Parent node or null.
CMP_TREE_NODE * ShowSingleLibrary()
Find and expand a library if there is only one.
void SetFilter(CMP_FILTER_TYPE aFilter)
Set the component filter type.
virtual bool HasContainerColumns(wxDataViewItem const &aItem) const override
Check whether a container has columns too.
void SetPreselectNode(LIB_ID const &aLibId, int aUnit)
Set the component name to be selected if there are no search results.
void Format(OUTPUTFORMATTER *out, int aNestLevel, int aCtl, CPTREE &aTree)
Function Format outputs a PTREE into s-expression format via an OUTPUTFORMATTER derivative.
Definition: ptree.cpp:205
#define max(a, b)
Definition: auxiliary.h:86
PTR_VECTOR Children
List of child nodes.
void AttachTo(wxDataViewCtrl *aDataViewCtrl)
Attach to a wxDataViewCtrl and initialize it.
size_t i
Definition: json11.cpp:597
wxDataViewItem FindItem(const LIB_ID &aLibId)
Returns tree item corresponding to part.
virtual wxDataViewItem GetParent(wxDataViewItem const &aItem) const override
Get the parent of an item.
int Unit
Actual unit, or zero.
static wxDataViewItem ToItem(CMP_TREE_NODE const *aNode)
Convert CMP_TREE_NODE -> wxDataViewItem.
const UTF8 & GetLibNickname() const
Return the logical library name portion of a LIB_ID.
Definition: lib_id.h:101
virtual 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.
int ColWidth(CMP_TREE_NODE &aTree, int aCol, wxString const &aHeading)
Compute the width required for the given column of a node and its children.
int Score
The score of an item resulting from the search algorithm.