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 
197 }
198 
199 
200 void CMP_TREE_MODEL_ADAPTER_BASE::AttachTo( wxDataViewCtrl* aDataViewCtrl )
201 {
202  m_widget = aDataViewCtrl;
203  aDataViewCtrl->SetIndent( kDataViewIndent );
204  aDataViewCtrl->AssociateModel( this );
205  aDataViewCtrl->ClearColumns();
206 
207  wxString part_head = _( "Symbol" );
208  wxString desc_head = _( "Desc" );
209 
210  m_col_part = aDataViewCtrl->AppendTextColumn( part_head, 0, wxDATAVIEW_CELL_INERT,
211  ColWidth( m_tree, 0, part_head ) );
212  m_col_desc = aDataViewCtrl->AppendTextColumn( desc_head, 1, wxDATAVIEW_CELL_INERT,
213  ColWidth( m_tree, 1, desc_head ) );
214 }
215 
216 
217 LIB_ID CMP_TREE_MODEL_ADAPTER_BASE::GetAliasFor( const wxDataViewItem& aSelection ) const
218 {
219  auto node = ToNode( aSelection );
220 
221  LIB_ID emptyId;
222 
223  if( !node )
224  return emptyId;
225 
226  return node->LibId;
227 }
228 
229 
230 int CMP_TREE_MODEL_ADAPTER_BASE::GetUnitFor( const wxDataViewItem& aSelection ) const
231 {
232  auto node = ToNode( aSelection );
233  return node ? node->Unit : 0;
234 }
235 
236 
237 CMP_TREE_NODE::TYPE CMP_TREE_MODEL_ADAPTER_BASE::GetTypeFor( const wxDataViewItem& aSelection ) const
238 {
239  auto node = ToNode( aSelection );
240  return node ? node->Type : CMP_TREE_NODE::INVALID;
241 }
242 
243 
245 {
246  int n = 0;
247 
248  for( auto& lib: m_tree.Children )
249  {
250  for( auto& alias: lib->Children )
251  {
252  (void) alias;
253  ++n;
254  }
255  }
256 
257  return n;
258 }
259 
260 
261 wxDataViewItem CMP_TREE_MODEL_ADAPTER_BASE::FindItem( const LIB_ID& aLibId )
262 {
263  for( auto& lib: m_tree.Children )
264  {
265  if( lib->Name != aLibId.GetLibNickname() )
266  continue;
267 
268  // if part name is not specified, return the library node
269  if( aLibId.GetLibItemName() == "" )
270  return ToItem( lib.get() );
271 
272  for( auto& alias: lib->Children )
273  {
274  if( alias->Name == aLibId.GetLibItemName() )
275  return ToItem( alias.get() );
276  }
277 
278  break; // could not find the part in the requested library
279  }
280 
281  return wxDataViewItem();
282 }
283 
284 
286  wxDataViewItem const& aItem,
287  wxDataViewItemArray& aChildren ) const
288 {
289  auto node = ( aItem.IsOk() ? ToNode( aItem ) : &m_tree );
290 
291  if( node->Type != CMP_TREE_NODE::TYPE::LIBID
292  || ( m_show_units && node->Type == CMP_TREE_NODE::TYPE::LIBID ) )
293  return IntoArray( *node, aChildren );
294  else
295  return 0;
296 }
297 
298 
299 bool CMP_TREE_MODEL_ADAPTER_BASE::HasContainerColumns( wxDataViewItem const& aItem ) const
300 {
301  return IsContainer( aItem );
302 }
303 
304 
305 bool CMP_TREE_MODEL_ADAPTER_BASE::IsContainer( wxDataViewItem const& aItem ) const
306 {
307  auto node = ToNode( aItem );
308  return node ? node->Children.size() : true;
309 }
310 
311 
312 wxDataViewItem CMP_TREE_MODEL_ADAPTER_BASE::GetParent( wxDataViewItem const& aItem ) const
313 {
314  auto node = ToNode( aItem );
315  auto parent = node ? node->Parent : nullptr;
316 
317  // wxDataViewModel has no root node, but rather top-level elements have
318  // an invalid (null) parent.
319  if( !node || !parent || parent->Type == CMP_TREE_NODE::TYPE::ROOT )
320  {
321  return ToItem( nullptr );
322  }
323  else
324  {
325  return ToItem( parent );
326  }
327 }
328 
329 
331  wxVariant& aVariant,
332  wxDataViewItem const& aItem,
333  unsigned int aCol ) const
334 {
335  if( IsFrozen() )
336  {
337  aVariant = wxEmptyString;
338  return;
339  }
340 
341  auto node = ToNode( aItem );
342  wxASSERT( node );
343 
344  switch( aCol )
345  {
346  default: // column == -1 is used for default Compare function
347  case 0:
348  aVariant = node->Name;
349  break;
350  case 1:
351  aVariant = node->Desc;
352  break;
353  }
354 }
355 
356 
358  wxDataViewItem const& aItem,
359  unsigned int aCol,
360  wxDataViewItemAttr& aAttr ) const
361 {
362  if( IsFrozen() )
363  return false;
364 
365  auto node = ToNode( aItem );
366  wxASSERT( node );
367 
368  if( node->Type != CMP_TREE_NODE::LIBID )
369  {
370  // Currently only aliases are formatted at all
371  return false;
372  }
373 
374  if( !node->IsRoot && aCol == 0 )
375  {
376  // Names of non-root aliases are italicized
377  aAttr.SetItalic( true );
378  return true;
379  }
380  else
381  {
382  return false;
383  }
384 }
385 
386 
387 int CMP_TREE_MODEL_ADAPTER_BASE::ColWidth( CMP_TREE_NODE& aTree, int aCol, wxString const& aHeading )
388 {
389  const int indent = aCol ? 0 : kDataViewIndent;
390 
391  int min_width = WidthFor( aHeading, aCol );
392  int width = std::max( aTree.Score > 0 ? WidthFor( aTree, aCol ) : 0, min_width );
393 
394  if( aTree.Score > 0 )
395  {
396  for( auto& node: aTree.Children )
397  {
398  width = std::max( width, ColWidth( *node, aCol, aHeading ) + indent );
399  }
400  }
401 
402  return width;
403 }
404 
405 
407 {
408  auto result = m_width_cache.find( aNode.Name );
409 
410  if( result != m_width_cache.end() )
411  {
412  return result->second[aCol];
413  }
414  else
415  {
416  int wname = m_widget->GetTextExtent( aNode.Name ).x + kDataViewIndent;
417  int wdesc = m_widget->GetTextExtent( aNode.Desc ).x;
418 
419  auto& val = m_width_cache[aNode.Name];
420  val.push_back( wname );
421  val.push_back( wdesc );
422  return val[aCol];
423  }
424 }
425 
426 
427 int CMP_TREE_MODEL_ADAPTER_BASE::WidthFor( wxString const& aHeading, int aCol )
428 {
429  static std::vector<int> widths;
430 
431  for( int i = (int) widths.size(); i <= aCol; ++i )
432  {
433  widths.push_back( 0 );
434  }
435 
436  if( widths[aCol] == 0 )
437  {
438  widths[aCol] = m_widget->GetTextExtent( aHeading ).x;
439  }
440 
441  return widths[aCol];
442 }
443 
444 
446  CMP_TREE_NODE& aNode,
447  std::function<bool( CMP_TREE_NODE const* )> aFunc )
448 {
449  for( auto& node: aNode.Children )
450  {
451  if( aFunc( &*node ) )
452  {
453  auto item = wxDataViewItem(
454  const_cast<void*>( static_cast<void const*>( &*node ) ) );
455  m_widget->ExpandAncestors( item );
456  m_widget->EnsureVisible( item );
457  m_widget->Select( item );
458  return true;
459  }
460  else if( FindAndExpand( *node, aFunc ) )
461  {
462  return true;
463  }
464  }
465 
466  return false;
467 }
468 
469 
471 {
472  return FindAndExpand( m_tree,
473  []( CMP_TREE_NODE const* n )
474  {
475  return n->Type == CMP_TREE_NODE::TYPE::LIBID && n->Score > 1;
476  } );
477 }
478 
479 
481 {
482  if( !m_preselect_lib_id.IsValid() )
483  return false;
484 
485  return FindAndExpand( m_tree,
486  [&]( CMP_TREE_NODE const* n )
487  {
488  if( n->Type == CMP_TREE_NODE::LIBID && ( n->Children.empty() || !m_preselect_unit ) )
489  return m_preselect_lib_id == n->LibId;
490  else if( n->Type == CMP_TREE_NODE::UNIT && m_preselect_unit )
491  return m_preselect_lib_id == n->Parent->LibId && m_preselect_unit == n->Unit;
492  else
493  return false;
494  } );
495 }
496 
497 
499 {
500  return FindAndExpand( m_tree,
501  []( CMP_TREE_NODE const* n )
502  {
503  return n->Type == CMP_TREE_NODE::TYPE::LIBID &&
504  n->Parent->Parent->Children.size() == 1;
505  } );
506 }
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.
bool FindAndExpand(CMP_TREE_NODE &aNode, std::function< bool(CMP_TREE_NODE const *)> aFunc)
Find any results worth highlighting and expand them, according to given criteria (f(CMP_TREE_NODE con...
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.
const UTF8 & GetLibItemName() const
Definition: lib_id.h:118
int GetUnitFor(const wxDataViewItem &aSelection) const
Return the unit for the given item.
bool ShowSingleLibrary()
Find and expand a library if there is only one.
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.
#define PROGRESS_INTERVAL_MILLIS
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.
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
bool ShowResults()
Find and expand successful search results.
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.
bool ShowPreselect()
Find and expand preselected node.
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.