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{
180 return m_parent->GetToolDispatcher();
181}
182
183
185{
186 for( const std::pair<const wxString, int>& pair : m_cfg.column_widths )
187 {
188 if( IsValidColumnWidth( pair.second ) )
189 m_colWidths[pair.first] = pair.second;
190 }
191
192 m_shownColumns = m_cfg.columns;
193
194 if( m_shownColumns.empty() )
195 m_shownColumns = { _HKI( "Item" ), _HKI( "Description" ) };
196
197 if( m_shownColumns[0] != _HKI( "Item" ) )
198 m_shownColumns.insert( m_shownColumns.begin(), _HKI( "Item" ) );
199}
200
201
202std::vector<wxString> LIB_TREE_MODEL_ADAPTER::GetOpenLibs() const
203{
204 std::vector<wxString> openLibs;
205 wxDataViewItem rootItem( nullptr );
206 wxDataViewItemArray children;
207
208 GetChildren( rootItem, children );
209
210 for( const wxDataViewItem& child : children )
211 {
212 if( m_widget->IsExpanded( child ) )
213 openLibs.emplace_back( ToNode( child )->m_LibId.GetLibNickname().wx_str() );
214 }
215
216 return openLibs;
217}
218
219
220void LIB_TREE_MODEL_ADAPTER::OpenLibs( const std::vector<wxString>& aLibs )
221{
222 wxWindowUpdateLocker updateLock( m_widget );
223
224 for( const wxString& lib : aLibs )
225 {
226 wxDataViewItem item = FindItem( LIB_ID( lib, wxEmptyString ) );
227
228 if( item.IsOk() )
229 m_widget->Expand( item );
230 }
231}
232
233
235{
236 if( m_widget )
237 {
238 m_cfg.columns = GetShownColumns();
239 m_cfg.column_widths.clear();
240
241 for( const std::pair<const wxString, wxDataViewColumn*>& pair : m_colNameMap )
242 {
243 if( pair.second && IsValidColumnWidth( pair.second->GetWidth() ) )
244 m_cfg.column_widths[pair.first] = pair.second->GetWidth();
245 }
246
247 m_cfg.open_libs = GetOpenLibs();
248 }
249}
250
251
253{
254 m_show_units = aShow;
255}
256
257
258void LIB_TREE_MODEL_ADAPTER::SetPreselectNode( const LIB_ID& aLibId, int aUnit )
259{
260 m_preselect_lib_id = aLibId;
261 m_preselect_unit = aUnit;
262}
263
264
265LIB_TREE_NODE_LIBRARY& LIB_TREE_MODEL_ADAPTER::DoAddLibraryNode( const wxString& aNodeName, const wxString& aDesc,
266 bool pinned )
267{
268 LIB_TREE_NODE_LIBRARY& lib_node = m_tree.AddLib( aNodeName, aDesc );
269
270 lib_node.m_Pinned = pinned;
271
272 return lib_node;
273}
274
275
276LIB_TREE_NODE_LIBRARY& LIB_TREE_MODEL_ADAPTER::DoAddLibrary( const wxString& aNodeName, const wxString& aDesc,
277 const std::vector<LIB_TREE_ITEM*>& aItemList,
278 bool pinned, bool presorted )
279{
280 LIB_TREE_NODE_LIBRARY& lib_node = DoAddLibraryNode( aNodeName, aDesc, pinned );
281
282 for( LIB_TREE_ITEM* item: aItemList )
283 {
284 if( item )
285 lib_node.AddItem( item );
286 }
287
288 lib_node.AssignIntrinsicRanks( m_shownColumns, presorted );
289
290 return lib_node;
291}
292
293
294void LIB_TREE_MODEL_ADAPTER::RemoveGroup( bool aRecentGroup, bool aPlacedGroup )
295{
296 m_tree.RemoveGroup( aRecentGroup, aPlacedGroup );
297}
298
299
300void LIB_TREE_MODEL_ADAPTER::UpdateSearchString( const wxString& aSearch, bool aState )
301{
302 const LIB_TREE_NODE* firstMatch = nullptr;
303
304 {
305 wxWindowUpdateLocker updateLock( m_widget );
306
307 // Even with the updateLock, wxWidgets sometimes ties its knickers in a knot trying to
308 // run a wxdataview_selection_changed_callback() on a row that has been deleted.
309 // https://bugs.launchpad.net/kicad/+bug/1756255
310 m_widget->UnselectAll();
311
312 // This collapse is required before the call to "Freeze()" below. Once Freeze()
313 // is called, GetParent() will return nullptr. While this works for some calls, it
314 // segfaults when we have any expanded elements b/c the sub units in the tree don't
315 // have explicit references that are maintained over a search
316 // The tree will be expanded again below when we get our matches
317 //
318 // Also note that this cannot happen when we have deleted a symbol as GTK will also
319 // iterate over the tree in this case and find a symbol that has an invalid link
320 // and crash https://gitlab.com/kicad/code/kicad/-/issues/6910
321 if( !aState && !aSearch.IsNull() && m_tree.m_Children.size() )
322 {
323 for( std::unique_ptr<LIB_TREE_NODE>& child: m_tree.m_Children )
324 m_widget->Collapse( wxDataViewItem( &*child ) );
325 }
326
327 // Drop any pending scroll target before BeforeReset/AfterReset tears down the rows
328 // it points at. See KIPLATFORM::UI::CancelPendingScroll and #24433.
330
331 // DO NOT REMOVE THE FREEZE/THAW. This freeze/thaw is a flag for this model adapter
332 // that tells it when it shouldn't trust any of the data in the model. When set, it will
333 // not return invalid data to the UI, since this invalid data can cause crashes.
334 // This is different than the update locker, which locks the UI aspects only.
335 Freeze();
336 BeforeReset();
337
338 // Don't cause KiCad to hang if someone accidentally pastes the PCB or schematic into
339 // the search box.
340 constexpr int MAX_TERMS = 100;
341
342 wxStringTokenizer tokenizer( aSearch, " \t\r\n", wxTOKEN_STRTOK );
343 std::vector<std::unique_ptr<EDA_COMBINED_MATCHER>> termMatchers;
344
345 while( tokenizer.HasMoreTokens() && termMatchers.size() < MAX_TERMS )
346 {
347 wxString term = tokenizer.GetNextToken().Lower();
348 termMatchers.emplace_back( std::make_unique<EDA_COMBINED_MATCHER>( term, CTX_LIBITEM ) );
349 }
350
351 m_tree.UpdateScore( termMatchers, m_filter );
352
353 m_tree.SortNodes( m_sort_mode == BEST_MATCH );
354 AfterReset();
355 Thaw();
356
357 // Move showResults inside the update locker to ensure all tree manipulation
358 // (including ExpandAncestors) happens while the window is frozen. This prevents
359 // GTK from rendering stale cached cell data during partial updates.
360 // https://gitlab.com/kicad/code/kicad/-/issues/18407
361 firstMatch = showResults();
362 }
363
364#ifdef __WXGTK__
365 // Ensure the control is repainted with the updated data. Without an explicit
366 // refresh the Gtk port can display stale rows until the user interacts with
367 // them, leading to mismatched tree contents.
368 m_widget->Refresh();
369 m_widget->Update();
370
371 // This causes crashes on Linux. Until someone can figure out why, please leave this commented
372 // out.
373 // wxSafeYield();
374#endif
375
376 if( firstMatch )
377 {
378 wxDataViewItem item = ToItem( firstMatch );
379 m_widget->Select( item );
380
381 // Make sure the *parent* item is visible. The selected item is the first (shown) child
382 // of the parent. So it's always right below the parent, and this way the user can also
383 // see what library the selected part belongs to, without having a case where the selection
384 // is off the screen (unless the window is a single row high, which is unlikely).
385 //
386 // This also happens to circumvent https://bugs.launchpad.net/kicad/+bug/1804400 which
387 // appears to be a GTK+3 bug.
390 }
391}
392
393
394void LIB_TREE_MODEL_ADAPTER::AttachTo( wxDataViewCtrl* aDataViewCtrl )
395{
396 m_widget = aDataViewCtrl;
397 aDataViewCtrl->SetIndent( kDataViewIndent );
398 aDataViewCtrl->AssociateModel( this );
400}
401
402
404{
405 m_widget->ClearColumns();
406
407 m_columns.clear();
408 m_colIdxMap.clear();
409 m_colNameMap.clear();
410
411 // The Item column is always shown
412 doAddColumn( wxT( "Item" ) );
414}
415
416
418{
419 for( const wxString& colName : m_shownColumns )
420 {
421 if( !m_colNameMap.count( colName ) )
422 doAddColumn( colName, colName == wxT( "Description" ) );
423 }
424}
425
426
428{
429 // See UpdateSearchString -- the resort tears down rows that any queued GtkTreeView
430 // scroll target may still reference (#24433).
432
433 Freeze();
434 BeforeReset();
435
436 m_tree.SortNodes( m_sort_mode == BEST_MATCH );
437
438 AfterReset();
439 Thaw();
440}
441
442
444{
445 m_parent->Prj().PinLibrary( aTreeNode->m_LibId.GetLibNickname(), getLibType() );
446 aTreeNode->m_Pinned = true;
447
448 resortTree();
449 EnsureVisibleIfEnabled( m_widget, ToItem( aTreeNode ) );
450}
451
452
454{
455 m_parent->Prj().UnpinLibrary( aTreeNode->m_LibId.GetLibNickname(), getLibType() );
456 aTreeNode->m_Pinned = false;
457
458 resortTree();
459 // Keep focus at top when unpinning
460}
461
462
464{
466
467 for( const std::unique_ptr<LIB_TREE_NODE>& lib: m_tree.m_Children )
468 {
469 if( lib->m_IsRecentlyUsedGroup )
470 lib->m_Name = wxT( "-- " ) + _( "Recently Used" ) + wxT( " --" );
471 else if( lib->m_IsAlreadyPlacedGroup )
472 lib->m_Name = wxT( "-- " ) + _( "Already Placed" ) + wxT( " --" );
473 }
474}
475
476
477wxDataViewColumn* LIB_TREE_MODEL_ADAPTER::doAddColumn( const wxString& aHeader, bool aTranslate )
478{
479 wxString translatedHeader = aTranslate ? wxGetTranslation( aHeader ) : aHeader;
480
481 // The extent of the text doesn't take into account the space on either side
482 // in the header, so artificially pad it
483 wxSize headerMinWidth = KIUI::GetTextSize( translatedHeader + wxT( "MMM" ), m_widget );
484
485 if( !m_colWidths.count( aHeader ) || m_colWidths[aHeader] < headerMinWidth.x )
486 m_colWidths[aHeader] = headerMinWidth.x;
487
488 int index = (int) m_columns.size();
489
490 wxDataViewColumn* col = new wxDataViewColumn( translatedHeader, new LIB_TREE_RENDERER(), index,
491 m_colWidths[aHeader], wxALIGN_NOT,
492 wxDATAVIEW_CELL_INERT | (int) wxDATAVIEW_COL_RESIZABLE );
493 m_widget->AppendColumn( col );
494
495 col->SetMinWidth( headerMinWidth.x );
496
497 m_columns.emplace_back( col );
498 m_colNameMap[aHeader] = col;
499 m_colIdxMap[m_columns.size() - 1] = aHeader;
500
501 return col;
502}
503
504
505void LIB_TREE_MODEL_ADAPTER::addColumnIfNecessary( const wxString& aHeader )
506{
507 if( m_colNameMap.count( aHeader ) )
508 return;
509
510 // Columns will be created later
511 m_colNameMap[aHeader] = nullptr;
512 m_availableColumns.emplace_back( aHeader );
513}
514
515
516void LIB_TREE_MODEL_ADAPTER::SetShownColumns( const std::vector<wxString>& aColumnNames )
517{
518 bool recreate = m_shownColumns != aColumnNames;
519
520 m_shownColumns = aColumnNames;
521
522 if( recreate && m_widget )
524
525 for( std::unique_ptr<LIB_TREE_NODE>& lib: m_tree.m_Children )
526 lib->AssignIntrinsicRanks( m_shownColumns );
527}
528
529
530LIB_ID LIB_TREE_MODEL_ADAPTER::GetAliasFor( const wxDataViewItem& aSelection ) const
531{
532 const LIB_TREE_NODE* node = ToNode( aSelection );
533 return node ? node->m_LibId : LIB_ID();
534}
535
536
537int LIB_TREE_MODEL_ADAPTER::GetUnitFor( const wxDataViewItem& aSelection ) const
538{
539 const LIB_TREE_NODE* node = ToNode( aSelection );
540 return node ? node->m_Unit : 0;
541}
542
543
544LIB_TREE_NODE::TYPE LIB_TREE_MODEL_ADAPTER::GetTypeFor( const wxDataViewItem& aSelection ) const
545{
546 const LIB_TREE_NODE* node = ToNode( aSelection );
547 return node ? node->m_Type : LIB_TREE_NODE::TYPE::INVALID;
548}
549
550
551LIB_TREE_NODE* LIB_TREE_MODEL_ADAPTER::GetTreeNodeFor( const wxDataViewItem& aSelection ) const
552{
553 return ToNode( aSelection );
554}
555
556
558{
559 int n = 0;
560
561 for( const std::unique_ptr<LIB_TREE_NODE>& lib: m_tree.m_Children )
562 n += lib->m_Children.size();
563
564 return n;
565}
566
567
568wxDataViewItem LIB_TREE_MODEL_ADAPTER::FindItem( const LIB_ID& aLibId )
569{
570 for( std::unique_ptr<LIB_TREE_NODE>& lib: m_tree.m_Children )
571 {
572 if( lib->m_Name != aLibId.GetLibNickname().wx_str() )
573 continue;
574
575 // if part name is not specified, return the library node
576 if( aLibId.GetLibItemName() == "" )
577 return ToItem( lib.get() );
578
579 for( std::unique_ptr<LIB_TREE_NODE>& alias: lib->m_Children )
580 {
581 if( alias->m_Name == aLibId.GetLibItemName().wx_str() )
582 return ToItem( alias.get() );
583 }
584
585 break; // could not find the part in the requested library
586 }
587
588 return wxDataViewItem();
589}
590
591
596
597
598unsigned int LIB_TREE_MODEL_ADAPTER::GetChildren( const wxDataViewItem& aItem,
599 wxDataViewItemArray& aChildren ) const
600{
601 const LIB_TREE_NODE* node = ( aItem.IsOk() ? ToNode( aItem ) : &m_tree );
602 unsigned int count = 0;
603
604 if( node->m_Type == LIB_TREE_NODE::TYPE::ROOT
605 || node->m_Type == LIB_TREE_NODE::TYPE::LIBRARY
606 || ( m_show_units && node->m_Type == LIB_TREE_NODE::TYPE::ITEM ) )
607 {
608 for( std::unique_ptr<LIB_TREE_NODE> const& child: node->m_Children )
609 {
610 if( child->m_Score > 0 )
611 {
612 aChildren.Add( ToItem( &*child ) );
613 ++count;
614 }
615 }
616 }
617
618 return count;
619}
620
621
623{
624 wxDataViewColumn* col = nullptr;
625 size_t idx = 0;
626 int totalWidth = 0;
627 wxString header;
628
629 for( ; idx < m_columns.size() - 1; idx++ )
630 {
631 wxASSERT( m_colIdxMap.count( idx ) );
632
633 col = m_columns[idx];
634 header = m_colIdxMap[idx];
635
636 wxASSERT( m_colWidths.count( header ) );
637
638 col->SetWidth( m_colWidths[header] );
639 totalWidth += col->GetWidth();
640 }
641
642 int remainingWidth = m_widget->GetSize().x - totalWidth;
643 header = m_columns[idx]->GetTitle();
644
645 m_columns[idx]->SetWidth( std::max( m_colWidths[header], remainingWidth ) );
646}
647
648
650{
651 // Yes, this is an enormous hack. But it works on all platforms, it doesn't suffer
652 // the On^2 sorting issues that ItemChanged() does on OSX, and it doesn't lose the
653 // user's scroll position (which re-attaching or deleting/re-inserting columns does).
654 static int walk = 1;
655
656 std::vector<int> widths;
657
658 for( const wxDataViewColumn* col : m_columns )
659 widths.emplace_back( col->GetWidth() );
660
661 wxASSERT( widths.size() );
662
663 // Only use the widths read back if they are non-zero.
664 // GTK returns the displayed width of the column, which is not calculated immediately
665 if( widths[0] > 0 )
666 {
667 size_t i = 0;
668
669 for( const auto& [ colName, colPtr ] : m_colNameMap )
670 {
671 if( i >= widths.size() )
672 break;
673
674 int width = widths[i++];
675
676 // Keep the prior sane width if a DPI change handed back a corrupt one.
677 if( IsValidColumnWidth( width ) )
678 m_colWidths[ colName ] = width;
679 }
680 }
681
682 auto colIt = m_colWidths.begin();
683
684 colIt->second += walk;
685 colIt++;
686
687 if( colIt != m_colWidths.end() )
688 colIt->second -= walk;
689
690 for( const auto& [ colName, colPtr ] : m_colNameMap )
691 {
692 if( colPtr == m_columns[0] || colPtr == nullptr )
693 continue;
694
695 wxASSERT( m_colWidths.count( colName ) );
696 colPtr->SetWidth( m_colWidths[ colName ] );
697 }
698
699 walk = -walk;
700}
701
702
703bool LIB_TREE_MODEL_ADAPTER::HasContainerColumns( const wxDataViewItem& aItem ) const
704{
705 return IsContainer( aItem );
706}
707
708
709bool LIB_TREE_MODEL_ADAPTER::IsContainer( const wxDataViewItem& aItem ) const
710{
711 LIB_TREE_NODE* node = ToNode( aItem );
712 return node ? node->m_Children.size() : true;
713}
714
715
716wxDataViewItem LIB_TREE_MODEL_ADAPTER::GetParent( const wxDataViewItem& aItem ) const
717{
718 if( m_freeze )
719 return ToItem( nullptr );
720
721 LIB_TREE_NODE* node = ToNode( aItem );
722
723 if( !node || node->m_Type == LIB_TREE_NODE::TYPE::INVALID )
724 return ToItem( nullptr );
725
726 LIB_TREE_NODE* parent = node->m_Parent;
727
728 // wxDataViewModel has no root node, but rather top-level elements have an invalid (null) parent.
729 if( !parent || parent->m_Type == LIB_TREE_NODE::TYPE::ROOT )
730 return ToItem( nullptr );
731
732 return ToItem( parent );
733}
734
735
736void LIB_TREE_MODEL_ADAPTER::GetValue( wxVariant& aVariant, const wxDataViewItem& aItem,
737 unsigned int aCol ) const
738{
739 if( IsFrozen() )
740 {
741 aVariant = wxEmptyString;
742 return;
743 }
744
745 LIB_TREE_NODE* node = ToNode( aItem );
746 wxCHECK( node, /* void */ );
747 wxString valueStr;
748
749 switch( aCol )
750 {
751 case NAME_COL:
752 if( node->m_Pinned )
753 valueStr = GetPinningSymbol() + UnescapeString( node->m_Name );
754 else
755 valueStr = UnescapeString( node->m_Name );
756
757 break;
758
759 default:
760 if( m_colIdxMap.count( aCol ) )
761 {
762 const wxString& key = m_colIdxMap.at( aCol );
763
764 if( key == wxT( "Description" ) )
765 valueStr = UnescapeString( node->m_Desc );
766 else if( node->m_Fields.count( key ) )
767 valueStr = UnescapeString( node->m_Fields.at( key ) );
768 else
769 valueStr = wxEmptyString;
770 }
771
772 break;
773 }
774
775 valueStr.Replace( wxS( "\n" ), wxS( " " ) ); // Clear line breaks
776
777 aVariant = valueStr;
778}
779
780
781bool LIB_TREE_MODEL_ADAPTER::GetAttr( const wxDataViewItem& aItem, unsigned int aCol,
782 wxDataViewItemAttr& aAttr ) const
783{
784 if( IsFrozen() )
785 return false;
786
787 LIB_TREE_NODE* node = ToNode( aItem );
788 wxCHECK( node, false );
789
790 if( node->m_Type == LIB_TREE_NODE::TYPE::ITEM )
791 {
792 if( !node->m_IsRoot && aCol == 0 )
793 {
794 // Names of non-root aliases are italicized
795 aAttr.SetItalic( true );
796 return true;
797 }
798 }
799
800 return false;
801}
802
803
804void recursiveDescent( LIB_TREE_NODE& aNode, const std::function<int( const LIB_TREE_NODE* )>& f )
805{
806 for( std::unique_ptr<LIB_TREE_NODE>& node: aNode.m_Children )
807 {
808 int r = f( node.get() );
809
810 if( r == 0 )
811 break;
812 else if( r == -1 )
813 continue;
814
815 recursiveDescent( *node, f );
816 }
817}
818
819
821{
822 const LIB_TREE_NODE* firstMatch = nullptr;
823
824 // Expand parents of leaf nodes with some level of matching
826 [&]( const LIB_TREE_NODE* n )
827 {
828 if( n->m_Type == LIB_TREE_NODE::TYPE::ITEM && n->m_Score > 1 )
829 {
830 // Mirror the sort order (see LIB_TREE_NODE::Compare): an exact match
831 // outranks any score, otherwise the higher score wins. Otherwise the
832 // view would scroll to a high-scoring item that isn't at the top.
833 if( !firstMatch )
834 firstMatch = n;
835 else if( n->m_ExactMatch && !firstMatch->m_ExactMatch )
836 firstMatch = n;
837 else if( n->m_ExactMatch == firstMatch->m_ExactMatch && n->m_Score > firstMatch->m_Score )
838 firstMatch = n;
839
840 m_widget->ExpandAncestors( ToItem( n ) );
841 }
842
843 return 1; // keep going to expand ancestors of all found items
844 } );
845
846 // If no matches, find and show the preselect node
847 if( !firstMatch && m_preselect_lib_id.IsValid() )
848 {
850 [&]( const LIB_TREE_NODE* n )
851 {
852 // Don't match the recent and already placed libraries
853 if( n->m_Name.StartsWith( "-- " ) )
854 return -1; // Skip this node and its children
855
856 if( n->m_Type == LIB_TREE_NODE::TYPE::ITEM
857 && ( n->m_Children.empty() || !m_preselect_unit )
858 && m_preselect_lib_id == n->m_LibId )
859 {
860 firstMatch = n;
861 m_widget->ExpandAncestors( ToItem( n ) );
862 return 0;
863 }
864 else if( n->m_Type == LIB_TREE_NODE::TYPE::UNIT
867 {
868 firstMatch = n;
869 m_widget->ExpandAncestors( ToItem( n ) );
870 return 0;
871 }
872
873 return 1;
874 } );
875 }
876
877 // If still no matches expand a single library if there is only one
878 if( !firstMatch )
879 {
880 int libraries = 0;
881
882 for( const std::unique_ptr<LIB_TREE_NODE>& child : m_tree.m_Children )
883 {
884 if( !child->m_Name.StartsWith( "-- " ) )
885 libraries++;
886 }
887
888 if( libraries != 1 )
889 return nullptr;
890
892 [&]( const LIB_TREE_NODE* n )
893 {
894 if( n->m_Type == LIB_TREE_NODE::TYPE::ITEM )
895 {
896 firstMatch = n;
897 m_widget->ExpandAncestors( ToItem( n ) );
898 return 0;
899 }
900
901 return 1;
902 } );
903 }
904
905 return firstMatch;
906}
907
908
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).
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.
PTR_VECTOR 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
void recursiveDescent(LIB_TREE_NODE &aNode, const std::function< int(const LIB_TREE_NODE *)> &f)
static const int kDataViewIndent
void CancelPendingScroll(wxDataViewCtrl *aCtrl)
Cancel any pending scroll-to-item request on aCtrl.
Definition wxgtk/ui.cpp:457
bool IsDarkTheme()
Determine if the desktop interface is currently using a dark theme or a light theme.
Definition wxgtk/ui.cpp:50
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)
std::vector< std::string > header
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.