KiCad PCB EDA Suite
Loading...
Searching...
No Matches
panel_symbol_chooser.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) 2014 Henner Zeller <[email protected]>
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
19 */
20
22
24#include <pgm_base.h>
26#include <kiface_base.h>
27#include <sch_base_frame.h>
28#include <project_sch.h>
30#include <widgets/lib_tree.h>
36#include <eeschema_settings.h>
38#include <string_utils.h>
39#include <symbol_library_common.h> // For SYMBOL_LIBRARY_FILTER
40#include <algorithm>
41#include <wx/button.h>
42#include <wx/clipbrd.h>
43#include <wx/display.h>
44#include <wx/panel.h>
45#include <wx/sizer.h>
46#include <wx/splitter.h>
47#include <wx/timer.h>
48#include <wx/wxhtml.h>
49#include <wx/log.h>
50#include <wx/choice.h>
51#include <wx/stattext.h>
52#include <lib_symbol.h>
53
54
57
59 const SYMBOL_LIBRARY_FILTER* aFilter,
60 std::vector<PICKED_SYMBOL>& aHistoryList,
61 std::vector<PICKED_SYMBOL>& aAlreadyPlaced,
62 bool aAllowFieldEdits, bool aShowFootprints, bool& aCancelled,
63 std::function<void()> aAcceptHandler,
64 std::function<void()> aEscapeHandler ) :
65 wxPanel( aParent, wxID_ANY, wxDefaultPosition, wxDefaultSize ),
66 m_symbol_preview( nullptr ),
67 m_hsplitter( nullptr ),
68 m_vsplitter( nullptr ),
69 m_fp_sel_ctrl( nullptr ),
70 m_fp_preview( nullptr ),
71 m_tree( nullptr ),
72 m_details( nullptr ),
73 m_frame( aFrame ),
74 m_acceptHandler( std::move( aAcceptHandler ) ),
75 m_escapeHandler( std::move( aEscapeHandler ) ),
76 m_showPower( false ),
77 m_allow_field_edits( aAllowFieldEdits ),
78 m_show_footprints( aShowFootprints ),
79 m_bodyStyleLabel( nullptr ),
80 m_bodyStyleChoice( nullptr ),
82{
85 PROJECT_FILE& project = m_frame->Prj().GetProjectFile();
86
87 // Make sure settings are loaded before we start running multi-threaded symbol loaders
90
92 SYMBOL_TREE_MODEL_ADAPTER* adapter = static_cast<SYMBOL_TREE_MODEL_ADAPTER*>( m_adapter.get() );
93
94 if( aFilter )
95 {
96 const wxArrayString& liblist = aFilter->GetAllowedLibList();
97
98 for( const wxString& nickname : liblist )
99 {
100 if( libmgr->HasLibrary( nickname, true ) )
101 {
102 bool pinned = alg::contains( session.pinned_symbol_libs, nickname )
103 || alg::contains( project.m_PinnedSymbolLibs, nickname );
104
105 std::optional<LIBRARY_TABLE_ROW*> row = libmgr->GetRow( nickname );
106
107 if( row.has_value() && !row.value()->Hidden() )
108 adapter->AddLibrary( nickname, pinned );
109 }
110 }
111
112 adapter->AssignIntrinsicRanks();
113
114 if( aFilter->GetFilterPowerSymbols() )
115 {
116 static std::function<bool( LIB_TREE_NODE& )> powerFilter =
117 []( LIB_TREE_NODE& aNode ) -> bool
118 {
119 return aNode.m_IsPower;
120 };
121
122 adapter->SetFilter( &powerFilter );
123
124 m_showPower = true;
125 m_show_footprints = false;
126 }
127 }
128
129 std::vector<LIB_SYMBOL> history_list_storage;
130 std::vector<LIB_TREE_ITEM*> history_list;
131 std::vector<LIB_SYMBOL> already_placed_storage;
132 std::vector<LIB_TREE_ITEM*> already_placed;
133
134 // Lambda to encapsulate the common logic
135 auto processList =
136 [&]( const std::vector<PICKED_SYMBOL>& inputList,
137 std::vector<LIB_SYMBOL>& storageList,
138 std::vector<LIB_TREE_ITEM*>& resultList )
139 {
140 storageList.reserve( inputList.size() );
141
142 for( const PICKED_SYMBOL& i : inputList )
143 {
144 LIB_SYMBOL* symbol = m_frame->GetLibSymbol( i.LibId );
145
146 if( symbol )
147 {
148 storageList.emplace_back( *symbol );
149
150 for( const auto& [fieldType, fieldValue] : i.Fields )
151 {
152 SCH_FIELD* field = storageList.back().GetField( fieldType );
153
154 if( field )
155 field->SetText( fieldValue );
156 }
157
158 resultList.push_back( &storageList.back() );
159 }
160 }
161 };
162
163 // Sort the already placed list since it is potentially from multiple sessions,
164 // but not the most recent list since we want this listed by most recent usage.
165 std::sort( aAlreadyPlaced.begin(), aAlreadyPlaced.end(),
166 []( PICKED_SYMBOL const& a, PICKED_SYMBOL const& b )
167 {
168 return a.LibId.GetLibItemName() < b.LibId.GetLibItemName();
169 } );
170
171 processList( aHistoryList, history_list_storage, history_list );
172 processList( aAlreadyPlaced, already_placed_storage, already_placed );
173
174 adapter->DoAddLibrary( wxT( "-- " ) + _( "Recently Used" ) + wxT( " --" ), wxEmptyString, history_list,
175 false, true )
176 .m_IsRecentlyUsedGroup = true;
177
178 if( !aHistoryList.empty() )
179 adapter->SetPreselectNode( aHistoryList[0].LibId, aHistoryList[0].Unit );
180
181 adapter->DoAddLibrary( wxT( "-- " ) + _( "Already Placed" ) + wxT( " --" ), wxEmptyString, already_placed,
182 false, true )
184
185 adapter->AddLibraries( m_frame );
186
187 // -------------------------------------------------------------------------------------
188 // Construct the actual panel
189 //
190
191 wxBoxSizer* sizer = new wxBoxSizer( wxVERTICAL );
192
193 // Use a slightly different layout, with a details pane spanning the entire window,
194 // if we're not showing footprints.
196 {
197 m_hsplitter = new wxSplitterWindow( this, wxID_ANY, wxDefaultPosition, wxDefaultSize,
198 wxSP_LIVE_UPDATE | wxSP_NOBORDER | wxSP_3DSASH );
199
200 //Avoid the splitter window being assigned as the Parent to additional windows
201 m_hsplitter->SetExtraStyle( wxWS_EX_TRANSIENT );
202
203 sizer->Add( m_hsplitter, 1, wxEXPAND, 5 );
204 }
205 else
206 {
207 m_vsplitter = new wxSplitterWindow( this, wxID_ANY, wxDefaultPosition, wxDefaultSize,
208 wxSP_LIVE_UPDATE | wxSP_NOBORDER | wxSP_3DSASH );
209
210 m_hsplitter = new wxSplitterWindow( m_vsplitter, wxID_ANY, wxDefaultPosition, wxDefaultSize,
211 wxSP_LIVE_UPDATE | wxSP_NOBORDER | wxSP_3DSASH );
212
213 // Avoid the splitter window being assigned as the parent to additional windows.
214 m_vsplitter->SetExtraStyle( wxWS_EX_TRANSIENT );
215 m_hsplitter->SetExtraStyle( wxWS_EX_TRANSIENT );
216
217 wxPanel* detailsPanel = new wxPanel( m_vsplitter );
218 wxBoxSizer* detailsSizer = new wxBoxSizer( wxVERTICAL );
219 detailsPanel->SetSizer( detailsSizer );
220
221 m_details = new HTML_WINDOW( detailsPanel, wxID_ANY, wxDefaultPosition, wxDefaultSize );
222 detailsSizer->Add( m_details, 1, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, 5 );
223 detailsPanel->Layout();
224 detailsSizer->Fit( detailsPanel );
225
226 m_vsplitter->SetSashGravity( 0.5 );
227 m_vsplitter->SetMinimumPaneSize( 20 );
228 m_vsplitter->SplitHorizontally( m_hsplitter, detailsPanel );
229
230 sizer->Add( m_vsplitter, 1, wxEXPAND | wxBOTTOM, 5 );
231 }
232
233 wxPanel* treePanel = new wxPanel( m_hsplitter );
234 wxBoxSizer* treeSizer = new wxBoxSizer( wxVERTICAL );
235 treePanel->SetSizer( treeSizer );
236
237 m_tree = new LIB_TREE( treePanel, m_showPower ? wxT( "power" ) : wxT( "symbols" ), m_adapter,
239
240 treeSizer->Add( m_tree, 1, wxALL | wxEXPAND, 5 );
241 treePanel->Layout();
242 treeSizer->Fit( treePanel );
243
244 m_adapter->FinishTreeInitialization();
245
246 if( m_showPower )
247 m_tree->SetSearchString( g_powerSearchString );
248 else
249 m_tree->SetSearchString( g_symbolSearchString );
250
251 m_hsplitter->SetSashGravity( 0.8 );
252 m_hsplitter->SetMinimumPaneSize( 20 );
253 m_hsplitter->SplitVertically( treePanel, constructRightPanel( m_hsplitter ) );
254
255 m_dbl_click_timer = new wxTimer( this );
256 m_open_libs_timer = new wxTimer( this );
257
258 SetSizer( sizer );
259
260 Layout();
261
262 Bind( wxEVT_TIMER, &PANEL_SYMBOL_CHOOSER::onCloseTimer, this, m_dbl_click_timer->GetId() );
263 Bind( wxEVT_TIMER, &PANEL_SYMBOL_CHOOSER::onOpenLibsTimer, this, m_open_libs_timer->GetId() );
264 Bind( EVT_LIBITEM_SELECTED, &PANEL_SYMBOL_CHOOSER::onSymbolSelected, this );
265 Bind( EVT_LIBITEM_CHOSEN, &PANEL_SYMBOL_CHOOSER::onSymbolChosen, this );
266 aFrame->Bind( wxEVT_MENU_OPEN, &PANEL_SYMBOL_CHOOSER::onMenuOpen, this );
267 aFrame->Bind( wxEVT_MENU_CLOSE, &PANEL_SYMBOL_CHOOSER::onMenuClose, this );
268
269 if( m_fp_sel_ctrl )
270 m_fp_sel_ctrl->Bind( EVT_FOOTPRINT_SELECTED, &PANEL_SYMBOL_CHOOSER::onFootprintSelected, this );
271
274
275 if( m_details )
276 m_details->Bind( wxEVT_CHAR_HOOK, &PANEL_SYMBOL_CHOOSER::OnDetailsCharHook, this );
277
278 // Open the user's previously opened libraries on timer expiration.
279 // This is done on a timer because we need a gross hack to keep GTK from garbling the
280 // display. Must be longer than the search debounce timer.
281 m_open_libs_timer->StartOnce( 300 );
282}
283
284
286{
287 m_frame->Unbind( wxEVT_MENU_OPEN, &PANEL_SYMBOL_CHOOSER::onMenuOpen, this );
288 m_frame->Unbind( wxEVT_MENU_CLOSE, &PANEL_SYMBOL_CHOOSER::onMenuClose, this );
289 Unbind( wxEVT_TIMER, &PANEL_SYMBOL_CHOOSER::onCloseTimer, this );
290 Unbind( EVT_LIBITEM_SELECTED, &PANEL_SYMBOL_CHOOSER::onSymbolSelected, this );
291 Unbind( EVT_LIBITEM_CHOSEN, &PANEL_SYMBOL_CHOOSER::onSymbolChosen, this );
292
293 // Stop the timer during destruction early to avoid potential race conditions (that do happen)
294 m_dbl_click_timer->Stop();
295 m_open_libs_timer->Stop();
296 delete m_dbl_click_timer;
297 delete m_open_libs_timer;
298
299 if( m_showPower )
300 g_powerSearchString = m_tree->GetSearchString();
301 else
302 g_symbolSearchString = m_tree->GetSearchString();
303
304 if( m_fp_sel_ctrl )
305 m_fp_sel_ctrl->Unbind( EVT_FOOTPRINT_SELECTED, &PANEL_SYMBOL_CHOOSER::onFootprintSelected, this );
306
307 if( m_details )
308 m_details->Unbind( wxEVT_CHAR_HOOK, &PANEL_SYMBOL_CHOOSER::OnDetailsCharHook, this );
309
310 if( EESCHEMA_SETTINGS* cfg = dynamic_cast<EESCHEMA_SETTINGS*>( Kiface().KifaceSettings() ) )
311 {
312 // Save any changes to column widths, etc.
313 m_adapter->SaveSettings();
314
315 cfg->m_SymChooserPanel.width = GetParent()->ToDIP( GetParent()->GetSize().x );
316 cfg->m_SymChooserPanel.height = GetParent()->ToDIP( GetParent()->GetSize().y );
317
318 cfg->m_SymChooserPanel.sash_pos_h = m_hsplitter->GetSashPosition();
319
320 if( m_vsplitter )
321 cfg->m_SymChooserPanel.sash_pos_v = m_vsplitter->GetSashPosition();
322
323 cfg->m_SymChooserPanel.sort_mode = m_tree->GetSortMode();
324 }
325
326 m_frame = nullptr;
327}
328
329
330void PANEL_SYMBOL_CHOOSER::onMenuOpen( wxMenuEvent& aEvent )
331{
332 m_tree->BlockPreview( true );
333 aEvent.Skip();
334}
335
336
337void PANEL_SYMBOL_CHOOSER::onMenuClose( wxMenuEvent& aEvent )
338{
339 m_tree->BlockPreview( false );
340 aEvent.Skip();
341}
342
343
344void PANEL_SYMBOL_CHOOSER::OnChar( wxKeyEvent& aEvent )
345{
346 if( aEvent.GetKeyCode() == WXK_ESCAPE )
347 {
348 wxObject* eventSource = aEvent.GetEventObject();
349
350 if( wxTextCtrl* textCtrl = dynamic_cast<wxTextCtrl*>( eventSource ) )
351 {
352 // First escape cancels search string value
353 if( textCtrl->GetValue() == m_tree->GetSearchString() && !m_tree->GetSearchString().IsEmpty() )
354 {
355 m_tree->SetSearchString( wxEmptyString );
356 Regenerate();
357 GetFocusTarget()->SetFocus();
358 return;
359 }
360 }
361
363 }
364 else
365 {
366 aEvent.Skip();
367 }
368}
369
370
371wxPanel* PANEL_SYMBOL_CHOOSER::constructRightPanel( wxWindow* aParent )
372{
374
375 if( m_frame->GetCanvas() )
376 backend = m_frame->GetCanvas()->GetBackend();
377 else if( COMMON_SETTINGS* cfg = Pgm().GetCommonSettings() )
378 backend = EDA_DRAW_PANEL_GAL::ResolveStoredCanvasType( cfg->m_Graphics.canvas_type );
379
380 wxPanel* panel = new wxPanel( aParent );
381 wxBoxSizer* sizer = new wxBoxSizer( wxVERTICAL );
382
383 m_symbol_preview = new SYMBOL_PREVIEW_WIDGET( panel, &m_frame->Kiway(), true, backend );
384 m_symbol_preview->SetLayoutDirection( wxLayout_LeftToRight );
385
386 wxBoxSizer* bodyStyleSizer = new wxBoxSizer( wxHORIZONTAL );
387 m_bodyStyleLabel = new wxStaticText( panel, wxID_ANY, _( "Body style:" ) );
388 m_bodyStyleChoice = new wxChoice( panel, wxID_ANY );
389 m_bodyStyleChoice->Enable( false );
390 bodyStyleSizer->Add( m_bodyStyleLabel, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxTOP | wxBOTTOM, 5 );
391 bodyStyleSizer->Add( m_bodyStyleChoice, 1, wxEXPAND | wxALL, 5 );
392 sizer->Add( bodyStyleSizer, 0, wxEXPAND | wxBOTTOM, 5 );
393
395 {
396 sizer->Add( m_symbol_preview, 11, wxEXPAND | wxALL, 5 );
397
399 sizer->Add( m_fp_sel_ctrl, 0, wxEXPAND | wxLEFT | wxRIGHT, 5 );
400
401 m_fp_preview = new FOOTPRINT_PREVIEW_WIDGET( panel, m_frame->Kiway() );
402 m_fp_preview->SetUserUnits( m_frame->GetUserUnits() );
403
404 if( m_fp_preview )
405 sizer->Add( m_fp_preview, 10, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, 5 );
406 }
407 else
408 {
409 sizer->Add( m_symbol_preview, 1, wxEXPAND | wxALL, 5 );
410 }
411
412 panel->SetSizer( sizer );
413 panel->Layout();
414 sizer->Fit( panel );
415
416 return panel;
417}
418
419
421{
422 if( EESCHEMA_SETTINGS* cfg = dynamic_cast<EESCHEMA_SETTINGS*>( Kiface().KifaceSettings() ) )
423 {
424 auto horizPixelsFromDU =
425 [&]( int x ) -> int
426 {
427 wxSize sz( x, 0 );
428 return GetParent()->ConvertDialogToPixels( sz ).x;
429 };
430
431 EESCHEMA_SETTINGS::PANEL_SYM_CHOOSER& panelCfg = cfg->m_SymChooserPanel;
432
433 // The persisted size is stored in DIP so it is independent of the monitor it was saved
434 // on. Restoring raw pixels would scale the window by the DPI ratio when reopened on a
435 // different-scale display, producing a window that spills across monitors.
436 int w = panelCfg.width > 40 ? GetParent()->FromDIP( panelCfg.width ) : horizPixelsFromDU( 440 );
437 int h = panelCfg.height > 40 ? GetParent()->FromDIP( panelCfg.height ) : horizPixelsFromDU( 340 );
438
439 // Cap to the work area so a stale pre-DIP setting cannot reopen the window across monitors.
440 if( int display = wxDisplay::GetFromWindow( GetParent() ); display != wxNOT_FOUND )
441 {
442 wxRect workArea = wxDisplay( display ).GetClientArea();
443 w = std::min( w, workArea.GetWidth() );
444 h = std::min( h, workArea.GetHeight() );
445 }
446
447 GetParent()->SetSize( wxSize( w, h ) );
448 GetParent()->Layout();
449
450 // We specify the width of the right window (m_symbol_view_panel), because specify
451 // the width of the left window does not work as expected when SetSashGravity() is called
452
453 if( panelCfg.sash_pos_h < 0 )
454 panelCfg.sash_pos_h = horizPixelsFromDU( 220 );
455
456 if( panelCfg.sash_pos_v < 0 )
457 panelCfg.sash_pos_v = horizPixelsFromDU( 230 );
458
459 m_hsplitter->SetSashPosition( panelCfg.sash_pos_h );
460
461 if( m_vsplitter )
462 m_vsplitter->SetSashPosition( panelCfg.sash_pos_v );
463
464 m_adapter->SetSortMode( (LIB_TREE_MODEL_ADAPTER::SORT_MODE) panelCfg.sort_mode );
465 }
466
467 if( m_fp_preview && m_fp_preview->IsInitialized() )
468 {
469 // This hides the GAL panel and shows the status label
470 m_fp_preview->SetStatusText( wxEmptyString );
471 }
472
473 if( m_fp_sel_ctrl )
474 m_fp_sel_ctrl->Load( m_frame->Kiway(), m_frame->Prj() );
475}
476
477
479{
480 if( m_details
481 && aEvent.GetKeyCode() == 'C'
482 && aEvent.ControlDown()
483 && !aEvent.AltDown()
484 && !aEvent.ShiftDown()
485 && !aEvent.MetaDown() )
486 {
487 wxString txt = m_details->SelectionToText();
488 wxLogNull doNotLog; // disable logging of failed clipboard actions
489
490 if( wxTheClipboard->Open() )
491 {
492 wxTheClipboard->SetData( new wxTextDataObject( txt ) );
493 wxTheClipboard->Flush(); // Allow data to be available after closing KiCad
494 wxTheClipboard->Close();
495 }
496 }
497 else
498 {
499 aEvent.Skip();
500 }
501}
502
503
505{
506 m_compatCallback = std::move( aFunc );
507
508 if( SYMBOL_TREE_MODEL_ADAPTER* symAdapter = dynamic_cast<SYMBOL_TREE_MODEL_ADAPTER*>( m_adapter.get() ) )
509 symAdapter->SetCompatibilityCallback( m_compatCallback );
510}
511
512
514{
515 m_adapter->SetPreselectNode( aPreselect, 0 );
516
517 if( m_tree && aPreselect.IsValid() )
518 m_tree->SelectLibId( aPreselect );
519}
520
521
522LIB_ID PANEL_SYMBOL_CHOOSER::GetSelectedLibId( int* aUnit, int* aBodyStyle ) const
523{
524 LIB_ID id = m_tree->GetSelectedLibId( aUnit );
525
526 if( aBodyStyle )
527 *aBodyStyle = m_selectedBodyStyle;
528
529 return id;
530}
531
532
534{
535 int bodyStyleCount = aSymbol ? std::max( aSymbol->GetBodyStyleCount(), 1 ) : 1;
536
537 m_bodyStyleChoice->Enable( bodyStyleCount > 1 );
538 m_bodyStyleChoice->Clear();
539
540 if( bodyStyleCount > 1 )
541 {
542 for( int i = 1; i <= bodyStyleCount; i++ )
543 m_bodyStyleChoice->Append( aSymbol->GetBodyStyleDescription( i, false ) );
544
545 if( m_selectedBodyStyle > static_cast<int>( m_bodyStyleChoice->GetCount() ) )
547
548 m_bodyStyleChoice->SetSelection( m_selectedBodyStyle - 1 );
549 }
550 else
551 {
553 }
554}
555
556
557void PANEL_SYMBOL_CHOOSER::onSelectBodyStyle( wxCommandEvent& aEvent )
558{
559 int selection = m_bodyStyleChoice->GetSelection();
560
561 if( selection < 0 )
562 return;
563
564 m_selectedBodyStyle = selection + 1;
565
566 LIB_TREE_NODE* node = m_tree->GetCurrentTreeNode();
567
568 if( node && node->m_LibId.IsValid() )
569 m_symbol_preview->DisplaySymbol( node->m_LibId, node->m_Unit, m_selectedBodyStyle );
570}
571
572
574{
575 m_symbol_preview->GetCanvas()->SetEvtHandlerEnabled( false );
576 m_symbol_preview->GetCanvas()->StopDrawing();
577
578 if( m_fp_preview )
579 {
580 m_fp_preview->GetPreviewPanel()->GetCanvas()->SetEvtHandlerEnabled( false );
581 m_fp_preview->GetPreviewPanel()->GetCanvas()->StopDrawing();
582 }
583}
584
585
586void PANEL_SYMBOL_CHOOSER::onCloseTimer( wxTimerEvent& aEvent )
587{
588 // Hack because of eaten MouseUp event. See PANEL_SYMBOL_CHOOSER::onSymbolChosen
589 // for the beginning of this spaghetti noodle.
590
591 wxMouseState state = wxGetMouseState();
592
593 if( state.LeftIsDown() )
594 {
595 // Mouse hasn't been raised yet, so fire the timer again. Otherwise the
596 // purpose of this timer is defeated.
598 }
599 else
600 {
602 }
603}
604
605
606void PANEL_SYMBOL_CHOOSER::onOpenLibsTimer( wxTimerEvent& aEvent )
607{
608 if( EESCHEMA_SETTINGS* cfg = dynamic_cast<EESCHEMA_SETTINGS*>( Kiface().KifaceSettings() ) )
609 m_adapter->OpenLibs( cfg->m_LibTree.open_libs );
610}
611
612
614{
615 if( !m_fp_preview || !m_fp_preview->IsInitialized() )
616 return;
617
618 LIB_SYMBOL* symbol = nullptr;
619
620 try
621 {
622 symbol = PROJECT_SCH::SymbolLibAdapter( &m_frame->Prj() )->LoadSymbol( aLibId );
623 }
624 catch( const IO_ERROR& ioe )
625 {
626 wxLogError( _( "Error loading symbol %s from library '%s'." ) + wxS( "\n%s" ),
627 aLibId.GetLibItemName().wx_str(),
628 aLibId.GetLibNickname().wx_str(),
629 ioe.What() );
630 }
631
632 if( !symbol )
633 return;
634
635 m_fp_override.Empty();
636 showFootprint( symbol->GetFootprint() );
637}
638
639
640void PANEL_SYMBOL_CHOOSER::showFootprint( wxString const& aFootprint )
641{
642 if( !m_fp_preview || !m_fp_preview->IsInitialized() )
643 return;
644
645 if( aFootprint == wxEmptyString )
646 {
647 m_fp_preview->SetStatusText( _( "No footprint specified" ) );
648 }
649 else
650 {
651 LIB_ID lib_id;
652
653 if( lib_id.Parse( aFootprint ) == -1 && lib_id.IsValid() )
654 {
655 m_fp_preview->ClearStatus();
656 m_fp_preview->DisplayFootprint( lib_id );
657 }
658 else
659 {
660 m_fp_preview->SetStatusText( _( "Invalid footprint specified" ) );
661 }
662 }
663}
664
665
667{
668 if( !m_fp_sel_ctrl )
669 return;
670
671 m_fp_sel_ctrl->ClearFilters();
672
673 LIB_SYMBOL* symbol = nullptr;
674
675 if( aLibId.IsValid() )
676 {
677 try
678 {
679 symbol = PROJECT_SCH::SymbolLibAdapter( &m_frame->Prj() )->LoadSymbol( aLibId );
680 }
681 catch( const IO_ERROR& ioe )
682 {
683 wxLogError( _( "Error loading symbol %s from library '%s'." ) + wxS( "\n%s" ),
684 aLibId.GetLibItemName().wx_str(),
685 aLibId.GetLibNickname().wx_str(),
686 ioe.What() );
687 }
688 }
689
690 if( symbol != nullptr )
691 {
692 int pinCount = symbol->GetGraphicalPins( 0 /* all units */, 1 /* single bodyStyle */ ).size();
693 wxString fp_name = symbol->GetFootprint();
694
695 if( !m_fp_override.IsEmpty() )
696 fp_name = m_fp_override;
697
698 // Explicitly associated footprints (issue #2282) are listed ahead of the glob matches in
699 // written order and bypass the pin-count filter; a mapped EP/NC footprint legally has more
700 // pads than the symbol has pins.
701 for( const ASSOCIATED_FOOTPRINT& assoc : symbol->GetEffectiveAssociatedFootprints() )
702 m_fp_sel_ctrl->AddAlwaysIncludedFootprint( assoc.m_FootprintLibId );
703
704 m_fp_sel_ctrl->FilterByPinCount( pinCount );
705 m_fp_sel_ctrl->FilterByFootprintFilters( symbol->GetFPFilters(), true );
706 m_fp_sel_ctrl->SetDefaultFootprint( fp_name );
707 m_fp_sel_ctrl->UpdateList();
708 m_fp_sel_ctrl->Enable();
709 }
710 else
711 {
712 m_fp_sel_ctrl->UpdateList();
713 m_fp_sel_ctrl->Disable();
714 }
715}
716
717
718void PANEL_SYMBOL_CHOOSER::onFootprintSelected( wxCommandEvent& aEvent )
719{
720 m_fp_override = aEvent.GetString();
721
722 std::erase_if( m_field_edits,
723 []( std::pair<FIELD_T, wxString> const& i )
724 {
725 return i.first == FIELD_T::FOOTPRINT;
726 } );
727
728 m_field_edits.emplace_back( std::make_pair( FIELD_T::FOOTPRINT, m_fp_override ) );
729
731}
732
733
734void PANEL_SYMBOL_CHOOSER::onSymbolSelected( wxCommandEvent& aEvent )
735{
736 LIB_TREE_NODE* node = m_tree->GetCurrentTreeNode();
737
738 if( node && node->m_LibId.IsValid() )
739 {
740 if( node->m_LibId != m_curSymbolId )
741 m_field_edits.clear();
742
743 m_curSymbolId = node->m_LibId;
744 LIB_SYMBOL* symbol = m_frame->GetLibSymbol( m_curSymbolId );
745
746 updateBodyStyleChoice( symbol );
747
749
750 if( !node->m_Footprint.IsEmpty() )
751 {
752 wxCommandEvent evt( EVT_FOOTPRINT_SELECTED );
753 evt.SetString( node->m_Footprint);
754 onFootprintSelected( evt );
755 }
756 else
757 {
759 }
760
762
764 {
765 std::vector<VARIANT_COMPAT_RESULT> issues = m_compatCallback( m_curSymbolId );
766
767 if( !issues.empty() )
768 {
769 wxString html = wxS( "<br><b>" ) + _( "Compatibility Warnings:" ) + wxS( "</b><ul>" );
770
771 for( const VARIANT_COMPAT_RESULT& issue : issues )
772 html += wxS( "<li>" ) + EscapeHTML( issue.detail ) + wxS( "</li>" );
773
774 html += wxS( "</ul>" );
775 m_details->AppendToPage( html );
776 }
777 }
778 }
779 else
780 {
782 m_symbol_preview->SetStatusText( _( "No symbol selected" ) );
783
784 if( m_fp_preview && m_fp_preview->IsInitialized() )
785 m_fp_preview->SetStatusText( wxEmptyString );
786
787 m_field_edits.clear();
788 updateBodyStyleChoice( nullptr );
790 }
791}
792
793
794void PANEL_SYMBOL_CHOOSER::onSymbolChosen( wxCommandEvent& aEvent )
795{
796 if( m_tree->GetSelectedLibId().IsValid() )
797 {
798 // Got a selection. We can't just end the modal dialog here, because wx leaks some events
799 // back to the parent window (in particular, the MouseUp following a double click).
800 //
801 // NOW, here's where it gets really fun. wxTreeListCtrl eats MouseUp. This isn't really
802 // feasible to bypass without a fully custom wxDataViewCtrl implementation, and even then
803 // might not be fully possible (docs are vague). To get around this, we use a one-shot
804 // timer to schedule the dialog close.
805 //
806 // See PANEL_SYMBOL_CHOOSER::onCloseTimer for the other end of this spaghetti noodle.
808 }
809}
810
811
813{
814 LIB_ID savedSelection = m_tree->GetSelectedLibId();
815 m_tree->Regenerate( true );
816
817 if( savedSelection.IsValid() )
818 m_tree->CenterLibId( savedSelection );
819}
@ GAL_TYPE_MODERN
RHI implementation, Vulkan or Metal on macOS.
static GAL_TYPE ResolveStoredCanvasType(int aStoredCanvasType)
Convert a stored canvas type setting into a backend that can actually be used.
Add dark theme support to wxHtmlWindow.
Definition html_window.h:31
Hold an error message and may be used when throwing exceptions containing meaningful error messages.
virtual const wxString What() const
A composite of Problem() and Where()
bool HasLibrary(const wxString &aNickname, bool aCheckEnabled=false) const
Test for the existence of aNickname in the library tables.
std::optional< LIBRARY_TABLE_ROW * > GetRow(const wxString &aNickname, LIBRARY_TABLE_SCOPE aScope=LIBRARY_TABLE_SCOPE::BOTH) const
Like LIBRARY_MANAGER::GetRow but filtered to the LIBRARY_TABLE_TYPE of this adapter.
A logical library item identifier and consists of various portions much like a URI.
Definition lib_id.h:45
int Parse(const UTF8 &aId, bool aFix=false)
Parse LIB_ID with the information from aId.
Definition lib_id.cpp:65
bool IsValid() const
Check if this LID_ID is valid.
Definition lib_id.h:168
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
Define a library symbol object.
Definition lib_symbol.h:114
const std::vector< ASSOCIATED_FOOTPRINT > & GetEffectiveAssociatedFootprints() const
Definition lib_symbol.h:300
wxArrayString GetFPFilters() const
Definition lib_symbol.h:252
int GetBodyStyleCount() const override
The body styles are a property of the drawings, which a derived symbol inherits from its root symbol ...
wxString GetFootprint() const override
For items with footprint fields.
wxString GetBodyStyleDescription(int aBodyStyle, bool aLabel) const override
std::vector< SCH_PIN * > GetGraphicalPins(int aUnit=0, int aBodyStyle=0) const override
Graphical pins: Return schematic pin objects as drawn (unexpanded), filtered by unit/body.
void SetPreselectNode(const LIB_ID &aLibId, int aUnit)
Set the symbol name to be selected if there are no search results.
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.
void AssignIntrinsicRanks()
Sort the tree and assign ranks after adding libraries.
void SetFilter(std::function< bool(LIB_TREE_NODE &aNode)> *aFilter)
Set the filter.
Model class in the component selector Model-View-Adapter (mediated MVC) architecture.
bool m_IsAlreadyPlacedGroup
wxString m_Footprint
Widget displaying a tree of symbols with optional search text control and description panel.
Definition lib_tree.h:44
@ ALL_WIDGETS
Definition lib_tree.h:53
SYMBOL_PREVIEW_WIDGET * m_symbol_preview
PANEL_SYMBOL_CHOOSER(SCH_BASE_FRAME *aFrame, wxWindow *aParent, const SYMBOL_LIBRARY_FILTER *aFilter, std::vector< PICKED_SYMBOL > &aHistoryList, std::vector< PICKED_SYMBOL > &aAlreadyPlaced, bool aAllowFieldEdits, bool aShowFootprints, bool &aCancelled, std::function< void()> aAcceptHandler, std::function< void()> aEscapeHandler)
Create dialog to choose symbol.
void onSymbolSelected(wxCommandEvent &aEvent)
void showFootprintFor(const LIB_ID &aLibId)
Look up the footprint for a given symbol specified in the LIB_ID and display it.
void onMenuClose(wxMenuEvent &aEvent)
wxSplitterWindow * m_hsplitter
wxWindow * GetFocusTarget() const
void SetCompatibilityCallback(SYMBOL_COMPAT_FUNC aFunc)
FOOTPRINT_SELECT_WIDGET * m_fp_sel_ctrl
std::function< void()> m_escapeHandler
void updateBodyStyleChoice(LIB_SYMBOL *aSymbol)
void onSelectBodyStyle(wxCommandEvent &aEvent)
void OnDetailsCharHook(wxKeyEvent &aEvt)
static wxString g_symbolSearchString
void onMenuOpen(wxMenuEvent &aEvent)
Handle parent frame menu events to block tree preview.
std::vector< std::pair< FIELD_T, wxString > > m_field_edits
void onCloseTimer(wxTimerEvent &aEvent)
wxSplitterWindow * m_vsplitter
void showFootprint(const wxString &aFootprint)
Display the given footprint by name.
void populateFootprintSelector(const LIB_ID &aLibId)
Populate the footprint selector for a given alias.
void onOpenLibsTimer(wxTimerEvent &aEvent)
void onFootprintSelected(wxCommandEvent &aEvent)
static wxString g_powerSearchString
void OnChar(wxKeyEvent &aEvent)
void SetPreselect(const LIB_ID &aPreselect)
std::function< void()> m_acceptHandler
static constexpr int DBLCLICK_DELAY
SYMBOL_COMPAT_FUNC m_compatCallback
FOOTPRINT_PREVIEW_WIDGET * m_fp_preview
LIB_ID GetSelectedLibId(int *aUnit=nullptr, int *aBodyStyle=nullptr) const
To be called after this dialog returns from ShowModal().
wxObjectDataPtr< LIB_TREE_MODEL_ADAPTER > m_adapter
void onSymbolChosen(wxCommandEvent &aEvent)
Handle the selection of an item.
wxPanel * constructRightPanel(wxWindow *aParent)
virtual COMMON_SETTINGS * GetCommonSettings() const
Definition pgm_base.cpp:562
The backing store for a PROJECT, in JSON format.
static SYMBOL_LIBRARY_ADAPTER * SymbolLibAdapter(PROJECT *aProject)
Accessor for project symbol library manager adapter.
A shim class between EDA_DRAW_FRAME and several derived classes: SYMBOL_EDIT_FRAME,...
void SetText(const wxString &aText) override
An interface to the global shared library manager that is schematic-specific and linked to one projec...
LIB_SYMBOL * LoadSymbol(const wxString &aNickname, const wxString &aName)
Load a LIB_SYMBOL having aName from the library given by aNickname.
Helper object to filter a list of libraries.
const wxArrayString & GetAllowedLibList() const
void AddLibraries(SCH_BASE_FRAME *aFrame)
Add all the libraries in a LIBRARY_TABLE to the model.
static wxObjectDataPtr< LIB_TREE_MODEL_ADAPTER > Create(SCH_BASE_FRAME *aParent, SYMBOL_LIBRARY_ADAPTER *aLibs)
Factory function: create a model adapter in a reference-counting container.
void AddLibrary(wxString const &aLibNickname, bool pinned)
wxString wx_str() const
Definition utf8.cpp:41
KIFACE_BASE & Kiface()
Global KIFACE_BASE "get" accessor.
Definition cvpcb.cpp:165
#define _(s)
bool contains(const _Container &__container, _Value __value)
Returns true if the container contains the given value.
Definition kicad_algo.h:96
STL namespace.
PGM_BASE & Pgm()
The global program "get" accessor.
see class PGM_BASE
T * GetAppSettings(const char *aFilename)
wxString EscapeHTML(const wxString &aString)
Return a new wxString escaped for embedding in HTML.
A first-class footprint choice on a LIB_SYMBOL, tied to a named pin map.
Definition pin_map.h:159
std::vector< wxString > pinned_symbol_libs
@ FOOTPRINT
Field Name Module PCB, i.e. "16DIP300".
std::function< std::vector< VARIANT_COMPAT_RESULT >(const LIB_ID &)> SYMBOL_COMPAT_FUNC
Callback that evaluates variant symbol compatibility for a given candidate LIB_ID.