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_colIdxMap[idx];
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 wxASSERT( !m_columns.empty() );
694
695 // Only use the widths read back if they are non-zero.
696 // GTK returns the displayed width of the column, which is not calculated immediately
697 if( m_columns[0]->GetWidth() > 0 )
698 {
699 for( const auto& [ colName, colPtr ] : m_colNameMap )
700 {
701 if( !colPtr )
702 continue;
703
704 int width = colPtr->GetWidth();
705
706 // Keep the prior sane width if a DPI change handed back a corrupt one.
707 if( IsValidColumnWidth( width ) )
708 m_colWidths[ colName ] = width;
709 }
710 }
711
712 auto colIt = m_colWidths.begin();
713
714 colIt->second += walk;
715 colIt++;
716
717 if( colIt != m_colWidths.end() )
718 colIt->second -= walk;
719
720 for( const auto& [ colName, colPtr ] : m_colNameMap )
721 {
722 if( colPtr == m_columns[0] || colPtr == nullptr )
723 continue;
724
725 wxASSERT( m_colWidths.count( colName ) );
726 colPtr->SetWidth( m_colWidths[ colName ] );
727 }
728
729 walk = -walk;
730}
731
732
733bool LIB_TREE_MODEL_ADAPTER::HasContainerColumns( const wxDataViewItem& aItem ) const
734{
735 return IsContainer( aItem );
736}
737
738
739bool LIB_TREE_MODEL_ADAPTER::IsContainer( const wxDataViewItem& aItem ) const
740{
741 LIB_TREE_NODE* node = ToNode( aItem );
742 return node ? node->m_Children.size() : true;
743}
744
745
746wxDataViewItem LIB_TREE_MODEL_ADAPTER::GetParent( const wxDataViewItem& aItem ) const
747{
748 if( m_freeze )
749 return ToItem( nullptr );
750
751 LIB_TREE_NODE* node = ToNode( aItem );
752
753 if( !node || node->m_Type == LIB_TREE_NODE::TYPE::INVALID )
754 return ToItem( nullptr );
755
756 LIB_TREE_NODE* parent = node->m_Parent;
757
758 // wxDataViewModel has no root node, but rather top-level elements have an invalid (null) parent.
759 if( !parent || parent->m_Type == LIB_TREE_NODE::TYPE::ROOT )
760 return ToItem( nullptr );
761
762 return ToItem( parent );
763}
764
765
766void LIB_TREE_MODEL_ADAPTER::GetValue( wxVariant& aVariant, const wxDataViewItem& aItem,
767 unsigned int aCol ) const
768{
769 if( IsFrozen() )
770 {
771 aVariant = wxEmptyString;
772 return;
773 }
774
775 LIB_TREE_NODE* node = ToNode( aItem );
776 wxCHECK( node, /* void */ );
777 wxString valueStr;
778
779 switch( aCol )
780 {
781 case NAME_COL:
782 if( node->m_Pinned )
783 valueStr = GetPinningSymbol() + UnescapeString( node->m_Name );
784 else
785 valueStr = UnescapeString( node->m_Name );
786
787 break;
788
789 default:
790 if( m_colIdxMap.count( aCol ) )
791 {
792 const wxString& key = m_colIdxMap.at( aCol );
793
794 if( key == wxT( "Description" ) )
795 valueStr = UnescapeString( node->m_Desc );
796 else if( node->m_Fields.count( key ) )
797 valueStr = UnescapeString( node->m_Fields.at( key ) );
798 else
799 valueStr = wxEmptyString;
800 }
801
802 break;
803 }
804
805 valueStr.Replace( wxS( "\n" ), wxS( " " ) ); // Clear line breaks
806
807 aVariant = valueStr;
808}
809
810
811bool LIB_TREE_MODEL_ADAPTER::GetAttr( const wxDataViewItem& aItem, unsigned int aCol,
812 wxDataViewItemAttr& aAttr ) const
813{
814 if( IsFrozen() )
815 return false;
816
817 LIB_TREE_NODE* node = ToNode( aItem );
818 wxCHECK( node, false );
819
820 if( node->m_Type == LIB_TREE_NODE::TYPE::ITEM )
821 {
822 if( !node->m_IsRoot && aCol == 0 )
823 {
824 // Names of non-root aliases are italicized
825 aAttr.SetItalic( true );
826 return true;
827 }
828 }
829
830 return false;
831}
832
833
834void recursiveDescent( LIB_TREE_NODE& aNode, const std::function<int( const LIB_TREE_NODE* )>& f )
835{
836 for( std::unique_ptr<LIB_TREE_NODE>& node: aNode.m_Children )
837 {
838 int r = f( node.get() );
839
840 if( r == 0 )
841 break;
842 else if( r == -1 )
843 continue;
844
845 recursiveDescent( *node, f );
846 }
847}
848
849
851{
852 const LIB_TREE_NODE* firstMatch = nullptr;
853
854 // Expand parents of leaf nodes with some level of matching
856 [&]( const LIB_TREE_NODE* n )
857 {
858 if( n->m_Type == LIB_TREE_NODE::TYPE::ITEM && n->m_Score > 1 )
859 {
860 // Mirror the sort order (see LIB_TREE_NODE::Compare): an exact match
861 // outranks any score, otherwise the higher score wins. Otherwise the
862 // view would scroll to a high-scoring item that isn't at the top.
863 if( !firstMatch )
864 firstMatch = n;
865 else if( n->m_ExactMatch && !firstMatch->m_ExactMatch )
866 firstMatch = n;
867 else if( n->m_ExactMatch == firstMatch->m_ExactMatch && n->m_Score > firstMatch->m_Score )
868 firstMatch = n;
869
870 m_widget->ExpandAncestors( ToItem( n ) );
871 }
872
873 return 1; // keep going to expand ancestors of all found items
874 } );
875
876 // If no matches, find and show the preselect node
877 if( !firstMatch && m_preselect_lib_id.IsValid() )
878 {
880 [&]( const LIB_TREE_NODE* n )
881 {
882 // Don't match the recent and already placed libraries
883 if( n->m_Name.StartsWith( "-- " ) )
884 return -1; // Skip this node and its children
885
886 if( n->m_Type == LIB_TREE_NODE::TYPE::ITEM
887 && ( n->m_Children.empty() || !m_preselect_unit )
888 && m_preselect_lib_id == n->m_LibId )
889 {
890 firstMatch = n;
891 m_widget->ExpandAncestors( ToItem( n ) );
892 return 0;
893 }
894 else if( n->m_Type == LIB_TREE_NODE::TYPE::UNIT
897 {
898 firstMatch = n;
899 m_widget->ExpandAncestors( ToItem( n ) );
900 return 0;
901 }
902
903 return 1;
904 } );
905 }
906
907 // If still no matches expand a single library if there is only one
908 if( !firstMatch )
909 {
910 int libraries = 0;
911
912 for( const std::unique_ptr<LIB_TREE_NODE>& child : m_tree.m_Children )
913 {
914 if( !child->m_Name.StartsWith( "-- " ) )
915 libraries++;
916 }
917
918 if( libraries != 1 )
919 return nullptr;
920
922 [&]( const LIB_TREE_NODE* n )
923 {
924 if( n->m_Type == LIB_TREE_NODE::TYPE::ITEM )
925 {
926 firstMatch = n;
927 m_widget->ExpandAncestors( ToItem( n ) );
928 return 0;
929 }
930
931 return 1;
932 } );
933 }
934
935 return firstMatch;
936}
937
938
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 -> LIB_TREE_NODE.
LIB_ID GetAliasFor(const wxDataViewItem &aSelection) const
Return the alias for the given item.
LIB_TREE_NODE_LIBRARY & DoAddLibrary(const wxString &aNodeName, const wxString &aDesc, const std::vector< LIB_TREE_ITEM * > &aItemList, bool aPinned, bool aPresorted)
Add the given list of symbols by alias.
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 LIB_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.
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:542
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.