KiCad PCB EDA Suite
Loading...
Searching...
No Matches
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 <[email protected]>
5 * Copyright (C) 2014 Henner Zeller <[email protected]>
6 * Copyright (C) 2023 CERN
7 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
8 *
9 * This program is free software: you can redistribute it and/or modify it
10 * under the terms of the GNU General Public License as published by the
11 * Free Software Foundation, either version 3 of the License, or (at your
12 * option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful, but
15 * WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program. If not, see <https://www.gnu.org/licenses/>.
21 */
22
23#include <eda_base_frame.h>
24#include <eda_pattern_match.h>
25#include <kiface_base.h>
26#include <kiplatform/ui.h>
30#include <widgets/ui_common.h>
32#include <wx/tokenzr.h>
33#include <wx/wupdlock.h>
34#include <wx/settings.h>
35#include <wx/dc.h>
36#include <wx/log.h>
37#include <string_utils.h>
38
39
40static const int kDataViewIndent = 20;
41
42
44{
45 // An out-of-range persisted width (seen after mixed-DPI monitor changes) can push the
46 // tree content out of view and leave the chooser unusable, so anything outside a width
47 // that could legitimately fit on a display is treated as corrupt rather than a resize.
48 return aWidth > 0 && aWidth <= MAX_COL_WIDTH;
49}
50
51
52class LIB_TREE_RENDERER : public wxDataViewCustomRenderer
53{
54public:
56 m_canvasItem( false )
57 {}
58
59 wxSize GetSize() const override
60 {
61 wxSize size( GetOwner()->GetWidth(), GetTextExtent( m_text ).y + 2 );
62
63#if defined( __WXGTK__ ) && !wxCHECK_VERSION( 3, 2, 7 )
64 // Somehow returning 0 or negative width prevents the returned height from
65 // being taken into account at all, even if we return strictly positive
66 // width from later calls to GetSize(), meaning that it's enough to return
67 // 0 from it once to completely break the layout for the entire lifetime of
68 // the control.
69 //
70 // As this is completely unexpected, forcefully prevent this from happening
71 size.IncTo( wxSize( 1, 1 ) );
72#endif
73
74 return size;
75 }
76
77 bool GetValue( wxVariant& aValue ) const override
78 {
79 aValue = m_text;
80 return true;
81 }
82
83 bool SetValue( const wxVariant& aValue ) override
84 {
85 m_text = aValue.GetString();
86 return true;
87 }
88
89 void SetAttr( const wxDataViewItemAttr& aAttr ) override
90 {
91 // Use strikethrough as a proxy for is-canvas-item
92 m_canvasItem = aAttr.GetStrikethrough();
93
94 wxDataViewItemAttr realAttr = aAttr;
95 realAttr.SetStrikethrough( false );
96
97 wxDataViewCustomRenderer::SetAttr( realAttr );
98 }
99
100 bool Render( wxRect aRect, wxDC *dc, int aState ) override
101 {
102 RenderBackground( dc, aRect );
103
104 if( m_canvasItem )
105 {
106 wxPoint points[6];
107 points[0] = aRect.GetTopLeft();
108 points[1] = aRect.GetTopRight() + wxPoint( -4, 0 );
109 points[2] = aRect.GetTopRight() + wxPoint( 0, aRect.GetHeight() / 2 );
110 points[3] = aRect.GetBottomRight() + wxPoint( -4, 1 );
111 points[4] = aRect.GetBottomLeft() + wxPoint( 0, 1 );
112 points[5] = aRect.GetTopLeft();
113
114 dc->SetPen( KIPLATFORM::UI::IsDarkTheme() ? *wxWHITE_PEN : *wxBLACK_PEN );
115 dc->DrawLines( 6, points );
116 }
117
118 aRect.Deflate( 1 );
119
120#ifdef __WXOSX__
121 // We should be able to pass wxDATAVIEW_CELL_SELECTED into RenderText() and have it do
122 // the right thing -- but it picks wxSYS_COLOUR_HIGHLIGHTTEXT on MacOS (instead
123 // of wxSYS_COLOUR_LISTBOXHIGHLIGHTTEXT).
124 if( aState & wxDATAVIEW_CELL_SELECTED )
125 dc->SetTextForeground( wxSystemSettings::GetColour( wxSYS_COLOUR_LISTBOXHIGHLIGHTTEXT ) );
126
127 RenderText( m_text, 0, aRect, dc, 0 );
128#else
129 RenderText( m_text, 0, aRect, dc, aState );
130#endif
131 return true;
132 }
133
134private:
136 wxString m_text;
137};
138
139
140wxDataViewItem LIB_TREE_MODEL_ADAPTER::ToItem( const LIB_TREE_NODE* aNode )
141{
142 return wxDataViewItem( const_cast<void*>( static_cast<void const*>( aNode ) ) );
143}
144
145
147{
148 return static_cast<LIB_TREE_NODE*>( aItem.GetID() );
149}
150
151
153 APP_SETTINGS_BASE::LIB_TREE& aSettingsStruct ) :
154 m_parent( aParent ),
155 m_cfg( aSettingsStruct ),
156 m_widget( nullptr ),
157 m_lazyLoadHandler( nullptr ),
159 m_show_units( true ),
160 m_preselect_unit( 0 ),
161 m_freeze( 0 ),
162 m_filter( nullptr )
163{
164 // Default column widths. Do not translate these names.
165 m_colWidths[ _HKI( "Item" ) ] = 300;
166 m_colWidths[ _HKI( "Description" ) ] = 600;
167
168 m_availableColumns = { _HKI( "Item" ), _HKI( "Description" ) };
169
171}
172
173
176
177
179 m_adapter( aAdapter )
180{
181 // A queued GtkTreeView scroll holds a row reference into the nodes the caller is about to
182 // free, so drop it while those rows are still valid (#24433, #24757)
184
185 m_adapter.Freeze();
186 m_adapter.BeforeReset();
187}
188
189
195
196
198{
199 return m_parent->GetToolDispatcher();
200}
201
202
204{
205 for( const std::pair<const wxString, int>& pair : m_cfg.column_widths )
206 {
207 if( IsValidColumnWidth( pair.second ) )
208 m_colWidths[pair.first] = pair.second;
209 }
210
211 m_shownColumns = m_cfg.columns;
212
213 if( m_shownColumns.empty() )
214 m_shownColumns = { _HKI( "Item" ), _HKI( "Description" ) };
215
216 if( m_shownColumns[0] != _HKI( "Item" ) )
217 m_shownColumns.insert( m_shownColumns.begin(), _HKI( "Item" ) );
218}
219
220
221std::vector<wxString> LIB_TREE_MODEL_ADAPTER::GetOpenLibs() const
222{
223 std::vector<wxString> openLibs;
224 wxDataViewItem rootItem( nullptr );
225 wxDataViewItemArray children;
226
227 GetChildren( rootItem, children );
228
229 for( const wxDataViewItem& child : children )
230 {
231 if( m_widget->IsExpanded( child ) )
232 openLibs.emplace_back( ToNode( child )->m_LibId.GetLibNickname().wx_str() );
233 }
234
235 return openLibs;
236}
237
238
239void LIB_TREE_MODEL_ADAPTER::OpenLibs( const std::vector<wxString>& aLibs )
240{
241 wxWindowUpdateLocker updateLock( m_widget );
242
243 for( const wxString& lib : aLibs )
244 {
245 wxDataViewItem item = FindItem( LIB_ID( lib, wxEmptyString ) );
246
247 if( item.IsOk() )
248 m_widget->Expand( item );
249 }
250}
251
252
254{
255 if( m_widget )
256 {
257 m_cfg.columns = GetShownColumns();
258 m_cfg.column_widths.clear();
259
260 for( const std::pair<const wxString, wxDataViewColumn*>& pair : m_colNameMap )
261 {
262 if( pair.second && IsValidColumnWidth( pair.second->GetWidth() ) )
263 m_cfg.column_widths[pair.first] = pair.second->GetWidth();
264 }
265
266 m_cfg.open_libs = GetOpenLibs();
267 }
268}
269
270
272{
273 m_show_units = aShow;
274}
275
276
277void LIB_TREE_MODEL_ADAPTER::SetPreselectNode( const LIB_ID& aLibId, int aUnit )
278{
279 m_preselect_lib_id = aLibId;
280 m_preselect_unit = aUnit;
281}
282
283
284LIB_TREE_NODE_LIBRARY& LIB_TREE_MODEL_ADAPTER::DoAddLibraryNode( const wxString& aNodeName, const wxString& aDesc,
285 bool pinned )
286{
287 LIB_TREE_NODE_LIBRARY& lib_node = m_tree.AddLib( aNodeName, aDesc );
288
289 lib_node.m_Pinned = pinned;
290
291 return lib_node;
292}
293
294
295LIB_TREE_NODE_LIBRARY& LIB_TREE_MODEL_ADAPTER::DoAddLibrary( const wxString& aNodeName, const wxString& aDesc,
296 const std::vector<LIB_TREE_ITEM*>& aItemList,
297 bool pinned, bool presorted )
298{
299 LIB_TREE_NODE_LIBRARY& lib_node = DoAddLibraryNode( aNodeName, aDesc, pinned );
300
301 for( LIB_TREE_ITEM* item: aItemList )
302 {
303 if( item )
304 lib_node.AddItem( item );
305 }
306
307 lib_node.AssignIntrinsicRanks( m_shownColumns, presorted );
308
309 return lib_node;
310}
311
312
313void LIB_TREE_MODEL_ADAPTER::RemoveGroup( bool aRecentGroup, bool aPlacedGroup )
314{
315 m_tree.RemoveGroup( aRecentGroup, aPlacedGroup );
316}
317
318
319void LIB_TREE_MODEL_ADAPTER::UpdateSearchString( const wxString& aSearch, bool aState )
320{
321 const LIB_TREE_NODE* firstMatch = nullptr;
322
323 {
324 wxWindowUpdateLocker updateLock( m_widget );
325
326 // Even with the updateLock, wxWidgets sometimes ties its knickers in a knot trying to
327 // run a wxdataview_selection_changed_callback() on a row that has been deleted.
328 // https://bugs.launchpad.net/kicad/+bug/1756255
329 m_widget->UnselectAll();
330
331 // This collapse is required before the call to "Freeze()" below. Once Freeze()
332 // is called, GetParent() will return nullptr. While this works for some calls, it
333 // segfaults when we have any expanded elements b/c the sub units in the tree don't
334 // have explicit references that are maintained over a search
335 // The tree will be expanded again below when we get our matches
336 //
337 // Also note that this cannot happen when we have deleted a symbol as GTK will also
338 // iterate over the tree in this case and find a symbol that has an invalid link
339 // and crash https://gitlab.com/kicad/code/kicad/-/issues/6910
340 if( !aState && !aSearch.IsNull() && m_tree.m_Children.size() )
341 {
342 for( std::unique_ptr<LIB_TREE_NODE>& child: m_tree.m_Children )
343 m_widget->Collapse( wxDataViewItem( &*child ) );
344 }
345
346 // Drop any pending scroll target before BeforeReset/AfterReset tears down the rows
347 // it points at. See KIPLATFORM::UI::CancelPendingScroll and #24433.
349
350 // DO NOT REMOVE THE FREEZE/THAW. This freeze/thaw is a flag for this model adapter
351 // that tells it when it shouldn't trust any of the data in the model. When set, it will
352 // not return invalid data to the UI, since this invalid data can cause crashes.
353 // This is different than the update locker, which locks the UI aspects only.
354 Freeze();
355 BeforeReset();
356
357 // Don't cause KiCad to hang if someone accidentally pastes the PCB or schematic into
358 // the search box.
359 constexpr int MAX_TERMS = 100;
360
361 wxString search( aSearch );
362
363 if( search.StartsWith( "::" ) )
364 search = search.Mid( 2 );
365
366 wxStringTokenizer tokenizer( search, " \t\r\n", wxTOKEN_STRTOK );
367 std::vector<std::unique_ptr<EDA_COMBINED_MATCHER>> termMatchers;
368
369 while( tokenizer.HasMoreTokens() && termMatchers.size() < MAX_TERMS )
370 {
371 wxString term = tokenizer.GetNextToken().Lower();
372 termMatchers.emplace_back( std::make_unique<EDA_COMBINED_MATCHER>( term, CTX_LIBITEM ) );
373 }
374
375 m_tree.UpdateScore( termMatchers, m_filter );
376
377 m_tree.SortNodes( m_sort_mode == BEST_MATCH );
378 AfterReset();
379 Thaw();
380
381 // Move showResults inside the update locker to ensure all tree manipulation
382 // (including ExpandAncestors) happens while the window is frozen. This prevents
383 // GTK from rendering stale cached cell data during partial updates.
384 // https://gitlab.com/kicad/code/kicad/-/issues/18407
385 firstMatch = showResults();
386 }
387
388#ifdef __WXGTK__
389 // Ensure the control is repainted with the updated data. Without an explicit
390 // refresh the Gtk port can display stale rows until the user interacts with
391 // them, leading to mismatched tree contents.
392 m_widget->Refresh();
393 m_widget->Update();
394
395 // This causes crashes on Linux. Until someone can figure out why, please leave this commented
396 // out.
397 // wxSafeYield();
398#endif
399
400 if( firstMatch )
401 {
402 wxDataViewItem item = ToItem( firstMatch );
403 m_widget->Select( item );
404
405 // Make sure the *parent* item is visible. The selected item is the first (shown) child
406 // of the parent. So it's always right below the parent, and this way the user can also
407 // see what library the selected part belongs to, without having a case where the selection
408 // is off the screen (unless the window is a single row high, which is unlikely).
409 //
410 // This also happens to circumvent https://bugs.launchpad.net/kicad/+bug/1804400 which
411 // appears to be a GTK+3 bug.
414 }
415}
416
417
418void LIB_TREE_MODEL_ADAPTER::AttachTo( wxDataViewCtrl* aDataViewCtrl )
419{
420 m_widget = aDataViewCtrl;
421 aDataViewCtrl->SetIndent( kDataViewIndent );
422 aDataViewCtrl->AssociateModel( this );
424}
425
426
428{
429 m_widget->ClearColumns();
430
431 m_columns.clear();
432 m_colIdxMap.clear();
433 m_colNameMap.clear();
434
435 // The Item column is always shown
436 doAddColumn( wxT( "Item" ) );
438}
439
440
442{
443 for( const wxString& colName : m_shownColumns )
444 {
445 if( !m_colNameMap.count( colName ) )
446 doAddColumn( colName, colName == wxT( "Description" ) );
447 }
448}
449
450
452{
453 // See UpdateSearchString -- the resort tears down rows that any queued GtkTreeView
454 // scroll target may still reference (#24433).
456
457 Freeze();
458 BeforeReset();
459
460 m_tree.SortNodes( m_sort_mode == BEST_MATCH );
461
462 AfterReset();
463 Thaw();
464}
465
466
468{
469 m_parent->Prj().PinLibrary( aTreeNode->m_LibId.GetLibNickname(), getLibType() );
470 aTreeNode->m_Pinned = true;
471
472 resortTree();
473 EnsureVisibleIfEnabled( m_widget, ToItem( aTreeNode ) );
474}
475
476
478{
479 m_parent->Prj().UnpinLibrary( aTreeNode->m_LibId.GetLibNickname(), getLibType() );
480 aTreeNode->m_Pinned = false;
481
482 resortTree();
483 // Keep focus at top when unpinning
484}
485
486
488{
490
491 for( const std::unique_ptr<LIB_TREE_NODE>& lib: m_tree.m_Children )
492 {
493 if( lib->m_IsRecentlyUsedGroup )
494 lib->m_Name = wxT( "-- " ) + _( "Recently Used" ) + wxT( " --" );
495 else if( lib->m_IsAlreadyPlacedGroup )
496 lib->m_Name = wxT( "-- " ) + _( "Already Placed" ) + wxT( " --" );
497 }
498}
499
500
501wxDataViewColumn* LIB_TREE_MODEL_ADAPTER::doAddColumn( const wxString& aHeader, bool aTranslate )
502{
503 wxString translatedHeader = aTranslate ? wxGetTranslation( aHeader ) : aHeader;
504
505 // The extent of the text doesn't take into account the space on either side
506 // in the header, so artificially pad it
507 wxSize headerMinWidth = KIUI::GetTextSize( translatedHeader + wxT( "MMM" ), m_widget );
508
509 if( !m_colWidths.count( aHeader ) || m_colWidths[aHeader] < headerMinWidth.x )
510 m_colWidths[aHeader] = headerMinWidth.x;
511
512 int index = (int) m_columns.size();
513
514 wxDataViewColumn* col = new wxDataViewColumn( translatedHeader, new LIB_TREE_RENDERER(), index,
515 m_colWidths[aHeader], wxALIGN_NOT,
516 wxDATAVIEW_CELL_INERT | (int) wxDATAVIEW_COL_RESIZABLE );
517 m_widget->AppendColumn( col );
518
519 col->SetMinWidth( headerMinWidth.x );
520
521 m_columns.emplace_back( col );
522 m_colNameMap[aHeader] = col;
523 m_colIdxMap[m_columns.size() - 1] = aHeader;
524
525 return col;
526}
527
528
529void LIB_TREE_MODEL_ADAPTER::addColumnIfNecessary( const wxString& aHeader )
530{
531 if( m_colNameMap.count( aHeader ) )
532 return;
533
534 // Columns will be created later
535 m_colNameMap[aHeader] = nullptr;
536 m_availableColumns.emplace_back( aHeader );
537}
538
539
540void LIB_TREE_MODEL_ADAPTER::SetShownColumns( const std::vector<wxString>& aColumnNames )
541{
542 bool recreate = m_shownColumns != aColumnNames;
543
544 m_shownColumns = aColumnNames;
545
546 if( recreate && m_widget )
548
549 for( std::unique_ptr<LIB_TREE_NODE>& lib: m_tree.m_Children )
550 lib->AssignIntrinsicRanks( m_shownColumns );
551}
552
553
554LIB_ID LIB_TREE_MODEL_ADAPTER::GetAliasFor( const wxDataViewItem& aSelection ) const
555{
556 const LIB_TREE_NODE* node = ToNode( aSelection );
557 return node ? node->m_LibId : LIB_ID();
558}
559
560
561int LIB_TREE_MODEL_ADAPTER::GetUnitFor( const wxDataViewItem& aSelection ) const
562{
563 const LIB_TREE_NODE* node = ToNode( aSelection );
564 return node ? node->m_Unit : 0;
565}
566
567
568LIB_TREE_NODE::TYPE LIB_TREE_MODEL_ADAPTER::GetTypeFor( const wxDataViewItem& aSelection ) const
569{
570 const LIB_TREE_NODE* node = ToNode( aSelection );
571 return node ? node->m_Type : LIB_TREE_NODE::TYPE::INVALID;
572}
573
574
575LIB_TREE_NODE* LIB_TREE_MODEL_ADAPTER::GetTreeNodeFor( const wxDataViewItem& aSelection ) const
576{
577 return ToNode( aSelection );
578}
579
580
582{
583 int n = 0;
584
585 for( const std::unique_ptr<LIB_TREE_NODE>& lib: m_tree.m_Children )
586 {
587 if( m_filter )
588 {
589 for( const std::unique_ptr<LIB_TREE_NODE>& child : lib->m_Children )
590 {
591 if( (*m_filter)( *child ) )
592 n++;
593 }
594 }
595 else
596 {
597 n += lib->m_Children.size();
598 }
599 }
600
601 return n;
602}
603
604
605wxDataViewItem LIB_TREE_MODEL_ADAPTER::FindItem( const LIB_ID& aLibId )
606{
607 for( std::unique_ptr<LIB_TREE_NODE>& lib: m_tree.m_Children )
608 {
609 if( lib->m_Name != aLibId.GetLibNickname().wx_str() )
610 continue;
611
612 // if part name is not specified, return the library node
613 if( aLibId.GetLibItemName() == "" )
614 return ToItem( lib.get() );
615
616 for( std::unique_ptr<LIB_TREE_NODE>& alias: lib->m_Children )
617 {
618 if( alias->m_Name == aLibId.GetLibItemName().wx_str() )
619 return ToItem( alias.get() );
620 }
621
622 break; // could not find the part in the requested library
623 }
624
625 return wxDataViewItem();
626}
627
628
633
634
635unsigned int LIB_TREE_MODEL_ADAPTER::GetChildren( const wxDataViewItem& aItem,
636 wxDataViewItemArray& aChildren ) const
637{
638 const LIB_TREE_NODE* node = ( aItem.IsOk() ? ToNode( aItem ) : &m_tree );
639 unsigned int count = 0;
640
641 if( node->m_Type == LIB_TREE_NODE::TYPE::ROOT
642 || node->m_Type == LIB_TREE_NODE::TYPE::LIBRARY
643 || ( m_show_units && node->m_Type == LIB_TREE_NODE::TYPE::ITEM ) )
644 {
645 for( std::unique_ptr<LIB_TREE_NODE> const& child: node->m_Children )
646 {
647 if( child->m_Score > 0 )
648 {
649 aChildren.Add( ToItem( &*child ) );
650 ++count;
651 }
652 }
653 }
654
655 return count;
656}
657
658
660{
661 wxDataViewColumn* col = nullptr;
662 size_t idx = 0;
663 int totalWidth = 0;
664 wxString header;
665
666 for( ; idx < m_columns.size() - 1; idx++ )
667 {
668 wxASSERT( m_colIdxMap.count( idx ) );
669
670 col = m_columns[idx];
671 header = m_colIdxMap[idx];
672
673 wxASSERT( m_colWidths.count( header ) );
674
675 col->SetWidth( m_colWidths[header] );
676 totalWidth += col->GetWidth();
677 }
678
679 int remainingWidth = m_widget->GetSize().x - totalWidth;
680 header = m_columns[idx]->GetTitle();
681
682 m_columns[idx]->SetWidth( std::max( m_colWidths[header], remainingWidth ) );
683}
684
685
687{
688 // Yes, this is an enormous hack. But it works on all platforms, it doesn't suffer
689 // the On^2 sorting issues that ItemChanged() does on OSX, and it doesn't lose the
690 // user's scroll position (which re-attaching or deleting/re-inserting columns does).
691 static int walk = 1;
692
693 std::vector<int> widths;
694
695 for( const wxDataViewColumn* col : m_columns )
696 widths.emplace_back( col->GetWidth() );
697
698 wxASSERT( widths.size() );
699
700 // Only use the widths read back if they are non-zero.
701 // GTK returns the displayed width of the column, which is not calculated immediately
702 if( widths[0] > 0 )
703 {
704 size_t i = 0;
705
706 for( const auto& [ colName, colPtr ] : m_colNameMap )
707 {
708 if( i >= widths.size() )
709 break;
710
711 int width = widths[i++];
712
713 // Keep the prior sane width if a DPI change handed back a corrupt one.
714 if( IsValidColumnWidth( width ) )
715 m_colWidths[ colName ] = width;
716 }
717 }
718
719 auto colIt = m_colWidths.begin();
720
721 colIt->second += walk;
722 colIt++;
723
724 if( colIt != m_colWidths.end() )
725 colIt->second -= walk;
726
727 for( const auto& [ colName, colPtr ] : m_colNameMap )
728 {
729 if( colPtr == m_columns[0] || colPtr == nullptr )
730 continue;
731
732 wxASSERT( m_colWidths.count( colName ) );
733 colPtr->SetWidth( m_colWidths[ colName ] );
734 }
735
736 walk = -walk;
737}
738
739
740bool LIB_TREE_MODEL_ADAPTER::HasContainerColumns( const wxDataViewItem& aItem ) const
741{
742 return IsContainer( aItem );
743}
744
745
746bool LIB_TREE_MODEL_ADAPTER::IsContainer( const wxDataViewItem& aItem ) const
747{
748 LIB_TREE_NODE* node = ToNode( aItem );
749 return node ? node->m_Children.size() : true;
750}
751
752
753wxDataViewItem LIB_TREE_MODEL_ADAPTER::GetParent( const wxDataViewItem& aItem ) const
754{
755 if( m_freeze )
756 return ToItem( nullptr );
757
758 LIB_TREE_NODE* node = ToNode( aItem );
759
760 if( !node || node->m_Type == LIB_TREE_NODE::TYPE::INVALID )
761 return ToItem( nullptr );
762
763 LIB_TREE_NODE* parent = node->m_Parent;
764
765 // wxDataViewModel has no root node, but rather top-level elements have an invalid (null) parent.
766 if( !parent || parent->m_Type == LIB_TREE_NODE::TYPE::ROOT )
767 return ToItem( nullptr );
768
769 return ToItem( parent );
770}
771
772
773void LIB_TREE_MODEL_ADAPTER::GetValue( wxVariant& aVariant, const wxDataViewItem& aItem,
774 unsigned int aCol ) const
775{
776 if( IsFrozen() )
777 {
778 aVariant = wxEmptyString;
779 return;
780 }
781
782 LIB_TREE_NODE* node = ToNode( aItem );
783 wxCHECK( node, /* void */ );
784 wxString valueStr;
785
786 switch( aCol )
787 {
788 case NAME_COL:
789 if( node->m_Pinned )
790 valueStr = GetPinningSymbol() + UnescapeString( node->m_Name );
791 else
792 valueStr = UnescapeString( node->m_Name );
793
794 break;
795
796 default:
797 if( m_colIdxMap.count( aCol ) )
798 {
799 const wxString& key = m_colIdxMap.at( aCol );
800
801 if( key == wxT( "Description" ) )
802 valueStr = UnescapeString( node->m_Desc );
803 else if( node->m_Fields.count( key ) )
804 valueStr = UnescapeString( node->m_Fields.at( key ) );
805 else
806 valueStr = wxEmptyString;
807 }
808
809 break;
810 }
811
812 valueStr.Replace( wxS( "\n" ), wxS( " " ) ); // Clear line breaks
813
814 aVariant = valueStr;
815}
816
817
818bool LIB_TREE_MODEL_ADAPTER::GetAttr( const wxDataViewItem& aItem, unsigned int aCol,
819 wxDataViewItemAttr& aAttr ) const
820{
821 if( IsFrozen() )
822 return false;
823
824 LIB_TREE_NODE* node = ToNode( aItem );
825 wxCHECK( node, false );
826
827 if( node->m_Type == LIB_TREE_NODE::TYPE::ITEM )
828 {
829 if( !node->m_IsRoot && aCol == 0 )
830 {
831 // Names of non-root aliases are italicized
832 aAttr.SetItalic( true );
833 return true;
834 }
835 }
836
837 return false;
838}
839
840
841void recursiveDescent( LIB_TREE_NODE& aNode, const std::function<int( const LIB_TREE_NODE* )>& f )
842{
843 for( std::unique_ptr<LIB_TREE_NODE>& node: aNode.m_Children )
844 {
845 int r = f( node.get() );
846
847 if( r == 0 )
848 break;
849 else if( r == -1 )
850 continue;
851
852 recursiveDescent( *node, f );
853 }
854}
855
856
858{
859 const LIB_TREE_NODE* firstMatch = nullptr;
860
861 // Expand parents of leaf nodes with some level of matching
863 [&]( const LIB_TREE_NODE* n )
864 {
865 if( n->m_Type == LIB_TREE_NODE::TYPE::ITEM && n->m_Score > 1 )
866 {
867 // Mirror the sort order (see LIB_TREE_NODE::Compare): an exact match
868 // outranks any score, otherwise the higher score wins. Otherwise the
869 // view would scroll to a high-scoring item that isn't at the top.
870 if( !firstMatch )
871 firstMatch = n;
872 else if( n->m_ExactMatch && !firstMatch->m_ExactMatch )
873 firstMatch = n;
874 else if( n->m_ExactMatch == firstMatch->m_ExactMatch && n->m_Score > firstMatch->m_Score )
875 firstMatch = n;
876
877 m_widget->ExpandAncestors( ToItem( n ) );
878 }
879
880 return 1; // keep going to expand ancestors of all found items
881 } );
882
883 // If no matches, find and show the preselect node
884 if( !firstMatch && m_preselect_lib_id.IsValid() )
885 {
887 [&]( const LIB_TREE_NODE* n )
888 {
889 // Don't match the recent and already placed libraries
890 if( n->m_Name.StartsWith( "-- " ) )
891 return -1; // Skip this node and its children
892
893 if( n->m_Type == LIB_TREE_NODE::TYPE::ITEM
894 && ( n->m_Children.empty() || !m_preselect_unit )
895 && m_preselect_lib_id == n->m_LibId )
896 {
897 firstMatch = n;
898 m_widget->ExpandAncestors( ToItem( n ) );
899 return 0;
900 }
901 else if( n->m_Type == LIB_TREE_NODE::TYPE::UNIT
904 {
905 firstMatch = n;
906 m_widget->ExpandAncestors( ToItem( n ) );
907 return 0;
908 }
909
910 return 1;
911 } );
912 }
913
914 // If still no matches expand a single library if there is only one
915 if( !firstMatch )
916 {
917 int libraries = 0;
918
919 for( const std::unique_ptr<LIB_TREE_NODE>& child : m_tree.m_Children )
920 {
921 if( !child->m_Name.StartsWith( "-- " ) )
922 libraries++;
923 }
924
925 if( libraries != 1 )
926 return nullptr;
927
929 [&]( const LIB_TREE_NODE* n )
930 {
931 if( n->m_Type == LIB_TREE_NODE::TYPE::ITEM )
932 {
933 firstMatch = n;
934 m_widget->ExpandAncestors( ToItem( n ) );
935 return 0;
936 }
937
938 return 1;
939 } );
940 }
941
942 return firstMatch;
943}
944
945
int index
The base frame for deriving all KiCad main window classes.
A logical library item identifier and consists of various portions much like a URI.
Definition lib_id.h:45
const UTF8 & GetLibItemName() const
Definition lib_id.h:98
const UTF8 & GetLibNickname() const
Return the logical library name portion of a LIB_ID.
Definition lib_id.h:83
A mix-in to provide polymorphism between items stored in libraries (symbols, aliases and footprints).
ResetTreeView(LIB_TREE_MODEL_ADAPTER &aAdapter)
LIB_TREE_MODEL_ADAPTER(EDA_BASE_FRAME *aParent, const wxString &aPinnedKey, APP_SETTINGS_BASE::LIB_TREE &aSettingsStruct)
Create the adapter.
std::vector< wxString > GetOpenLibs() const
int GetUnitFor(const wxDataViewItem &aSelection) const
Return the unit for the given item.
static bool IsValidColumnWidth(int aWidth)
LIB_TREE_NODE::TYPE GetTypeFor(const wxDataViewItem &aSelection) const
Return node type for the given item.
APP_SETTINGS_BASE::LIB_TREE & m_cfg
bool GetAttr(const wxDataViewItem &aItem, unsigned int aCol, wxDataViewItemAttr &aAttr) const override
Get any formatting for an item.
std::map< wxString, int > m_colWidths
const LIB_TREE_NODE * showResults()
Find and expand successful search results.
void FinishTreeInitialization()
A final-stage initialization to be called after the window hierarchy has been realized and the window...
void addColumnIfNecessary(const wxString &aHeader)
void PinLibrary(LIB_TREE_NODE *aTreeNode)
virtual PROJECT::LIB_TYPE_T getLibType()=0
virtual wxDataViewItem GetCurrentDataViewItem()
void SetPreselectNode(const LIB_ID &aLibId, int aUnit)
Set the symbol name to be selected if there are no search results.
static LIB_TREE_NODE * ToNode(wxDataViewItem aItem)
Convert wxDataViewItem -> #SYM_TREE_NODE.
LIB_ID GetAliasFor(const wxDataViewItem &aSelection) const
Return the alias for the given item.
TOOL_DISPATCHER * GetToolDispatcher() const
void AttachTo(wxDataViewCtrl *aDataViewCtrl)
Attach to a wxDataViewCtrl and initialize it.
void SetShownColumns(const std::vector< wxString > &aColumnNames)
Sets which columns are shown in the widget.
std::function< void()> m_lazyLoadHandler
static wxDataViewItem ToItem(const LIB_TREE_NODE *aNode)
Convert #SYM_TREE_NODE -> wxDataViewItem.
bool IsContainer(const wxDataViewItem &aItem) const override
Check whether an item can have children.
static const wxString GetPinningSymbol()
void ShowUnits(bool aShow)
Whether or not to show units.
LIB_TREE_NODE * GetTreeNodeFor(const wxDataViewItem &aSelection) const
unsigned int GetChildren(const wxDataViewItem &aItem, wxDataViewItemArray &aChildren) const override
Populate a list of all the children of an item.
void SaveSettings()
Save the column widths to the config file.
@ NAME_COL
Library or library item name column.
std::map< unsigned, wxString > m_colIdxMap
wxDataViewColumn * doAddColumn(const wxString &aHeader, bool aTranslate=true)
std::vector< wxDataViewColumn * > m_columns
std::vector< wxString > GetShownColumns() const
int GetItemCount() const
Return the number of symbols loaded in the tree.
std::vector< wxString > m_shownColumns
void RemoveGroup(bool aRecentlyUsedGroup, bool aAlreadyPlacedGroup)
Remove one of the system groups from the library.
void GetValue(wxVariant &aVariant, const wxDataViewItem &aItem, unsigned int aCol) const override
Get the value of an item.
void UnpinLibrary(LIB_TREE_NODE *aTreeNode)
std::map< wxString, wxDataViewColumn * > m_colNameMap
bool HasContainerColumns(const wxDataViewItem &aItem) const override
Check whether a container has columns too.
wxDataViewItem GetParent(const wxDataViewItem &aItem) const override
Get the parent of an item.
LIB_TREE_NODE_LIBRARY & DoAddLibraryNode(const wxString &aNodeName, const wxString &aDesc, bool pinned)
std::vector< wxString > m_availableColumns
static constexpr int MAX_COL_WIDTH
Upper bound for a persisted column width; larger values are treated as corrupt settings.
LIB_TREE_NODE_LIBRARY & DoAddLibrary(const wxString &aNodeName, const wxString &aDesc, const std::vector< LIB_TREE_ITEM * > &aItemList, bool pinned, bool presorted)
Add the given list of symbols by alias.
wxDataViewItem FindItem(const LIB_ID &aLibId)
Returns tree item corresponding to part.
void UpdateSearchString(const wxString &aSearch, bool aState)
Set the search string provided by the user.
std::function< bool(LIB_TREE_NODE &aNode)> * m_filter
void OpenLibs(const std::vector< wxString > &aLibs)
Node type: library.
LIB_TREE_NODE_ITEM & AddItem(LIB_TREE_ITEM *aItem)
Construct a new alias node, add it to this library, and return it.
Model class in the component selector Model-View-Adapter (mediated MVC) architecture.
std::map< wxString, wxString > m_Fields
List of weighted search terms.
std::vector< std::unique_ptr< LIB_TREE_NODE > > m_Children
LIB_TREE_NODE * m_Parent
void AssignIntrinsicRanks(const std::vector< wxString > &aShownColumns, bool presorted=false)
Store intrinsic ranks on all children of this node.
void SetAttr(const wxDataViewItemAttr &aAttr) override
bool SetValue(const wxVariant &aValue) override
wxSize GetSize() const override
bool GetValue(wxVariant &aValue) const override
bool Render(wxRect aRect, wxDC *dc, int aState) override
wxString wx_str() const
Definition utf8.cpp:41
static void recursiveDescent(wxSizer *aSizer, std::map< int, wxString > &aLabels)
#define _(s)
Base window classes and related definitions.
Abstract pattern-matching tool and implementations.
@ CTX_LIBITEM
static const int kDataViewIndent
void CancelPendingScroll(wxDataViewCtrl *aCtrl)
Cancel any pending scroll-to-item request on aCtrl.
Definition wxgtk/ui.cpp:530
bool IsDarkTheme()
Determine if the desktop interface is currently using a dark theme or a light theme.
Definition wxgtk/ui.cpp:51
KICOMMON_API 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 ui_common.cpp:78
#define _HKI(x)
Definition page_info.cpp:40
wxString UnescapeString(const wxString &aSource)
Functions to provide common constants and other functions to assist in making a consistent UI.
void EnsureVisibleIfEnabled(wxDataViewCtrl *aWidget, const wxDataViewItem &aItem)
Scroll aItem into view only when aWidget is currently enabled.