1 /*
2  * This program source code file is part of KiCad, a free EDA CAD application.
3  *
4  * Copyright (C) 2018 Jean-Pierre Charras, jp.charras at wanadoo.fr
5  * Copyright (C) 2011 Wayne Stambaugh <stambaughw@gmail.com>
6  * Copyright (C) 1992-2021 KiCad Developers, see AUTHORS.txt for contributors.
7  *
8  * This program is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU General Public License
10  * as published by the Free Software Foundation; either version 2
11  * of the License, or (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, you may find one here:
20  * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
21  * or you may search the http://www.gnu.org website for the version 2 license,
22  * or you may write to the Free Software Foundation, Inc.,
23  * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
24  */
25 
26 #include <bitmaps.h>
27 #include <confirm.h>
28 #include <eda_dde.h>
29 #include <fp_lib_table.h>
30 #include <kiface_base.h>
31 #include <kiplatform/app.h>
32 #include <kiway_express.h>
33 #include <macros.h>
34 #include <netlist_reader/netlist_reader.h>
35 #include <numeric>
36 #include <tool/action_manager.h>
37 #include <tool/action_toolbar.h>
38 #include <tool/common_control.h>
39 #include <tool/editor_conditions.h>
40 #include <tool/tool_dispatcher.h>
41 #include <tool/tool_manager.h>
42 #include <widgets/wx_progress_reporters.h>
43 #include <wx/statline.h>
44 #include <wx/stattext.h>
45 
46 #include <cvpcb_association.h>
47 #include <cvpcb_id.h>
48 #include <cvpcb_mainframe.h>
49 #include <cvpcb_settings.h>
50 #include <display_footprints_frame.h>
51 #include <kiplatform/ui.h>
52 #include <listboxes.h>
53 #include <tools/cvpcb_actions.h>
54 #include <tools/cvpcb_association_tool.h>
55 #include <tools/cvpcb_control.h>
56 #include <wx/button.h>
57 #include <wx/settings.h>
58 
59 
60 #define CVPCB_MAINFRAME_NAME wxT( "CvpcbFrame" )
61 
CVPCB_MAINFRAME(KIWAY * aKiway,wxWindow * aParent)62 CVPCB_MAINFRAME::CVPCB_MAINFRAME( KIWAY* aKiway, wxWindow* aParent ) :
63     KIWAY_PLAYER( aKiway, aParent, FRAME_CVPCB, _( "Assign Footprints" ), wxDefaultPosition,
64                   wxDefaultSize, KICAD_DEFAULT_DRAWFRAME_STYLE, CVPCB_MAINFRAME_NAME )
65 {
66     m_symbolsListBox      = nullptr;
67     m_footprintListBox    = nullptr;
68     m_librariesListBox    = nullptr;
69     m_mainToolBar         = nullptr;
70     m_modified            = false;
71     m_cannotClose         = false;
72     m_skipComponentSelect = false;
73     m_filteringOptions    = FOOTPRINTS_LISTBOX::UNFILTERED_FP_LIST;
74     m_tcFilterString      = nullptr;
75     m_FootprintsList      = FOOTPRINT_LIST::GetInstance( Kiway() );
76     m_initialized         = false;
77     m_aboutTitle          = "CvPcb";
78 
79     // Give an icon
80     wxIcon icon;
81     icon.CopyFromBitmap( KiBitmap( BITMAPS::icon_cvpcb ) );
82     SetIcon( icon );
83 
84     SetAutoLayout( true );
85 
86     LoadSettings( config() );
87 
88     setupTools();
89     setupUIConditions();
90     ReCreateMenuBar();
91     ReCreateHToolbar();
92 
93     // Create list of available footprints and symbols of the schematic
94     BuildSymbolsListBox();
95     BuildFootprintsListBox();
96     BuildLibrariesListBox();
97 
98     m_auimgr.SetManagedWindow( this );
99 
100     m_auimgr.AddPane( m_mainToolBar, EDA_PANE().HToolbar().Name( "MainToolbar" ).Top().Layer(6) );
101 
102     m_auimgr.AddPane( m_librariesListBox, EDA_PANE().Palette().Name( "Libraries" ).Left().Layer(1)
103                       .Caption( _( "Footprint Libraries" ) )
104                       .BestSize((int) ( m_frameSize.x * 0.20 ), m_frameSize.y ) );
105 
106     m_auimgr.AddPane( m_symbolsListBox, EDA_PANE().Palette().Name( "Symbols" ).Center().Layer(0)
107                       .Caption( _( "Symbol : Footprint Assignments" ) ) );
108 
109     m_auimgr.AddPane( m_footprintListBox, EDA_PANE().Palette().Name( "Footprints" ).Right().Layer(1)
110                       .Caption( _( "Filtered Footprints" ) )
111                       .BestSize((int) ( m_frameSize.x * 0.30 ), m_frameSize.y ) );
112 
113     // Build the bottom panel, to display 2 status texts and the buttons:
114     auto bottomPanel = new wxPanel( this );
115     auto panelSizer = new wxBoxSizer( wxVERTICAL );
116 
117     wxFlexGridSizer* fgSizerStatus = new wxFlexGridSizer( 3, 1, 0, 0 );
118     fgSizerStatus->SetFlexibleDirection( wxBOTH );
119     fgSizerStatus->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_SPECIFIED );
120 
121     m_statusLine1 = new wxStaticText( bottomPanel, wxID_ANY, wxEmptyString );
122     fgSizerStatus->Add( m_statusLine1, 0, 0, 5 );
123 
124     m_statusLine2 = new wxStaticText( bottomPanel, wxID_ANY, wxEmptyString );
125     fgSizerStatus->Add( m_statusLine2, 0, 0, 5 );
126 
127     m_statusLine3 = new wxStaticText( bottomPanel, wxID_ANY, wxEmptyString );
128     fgSizerStatus->Add( m_statusLine3, 0, wxBOTTOM, 3 );
129 
130     panelSizer->Add( fgSizerStatus, 1, wxEXPAND|wxLEFT, 2 );
131 
132     wxStaticLine* staticline1 = new wxStaticLine( bottomPanel );
133     panelSizer->Add( staticline1, 0, wxEXPAND, 5 );
134 
135     m_statusLine1->SetFont( KIUI::GetStatusFont( this ) );
136     m_statusLine2->SetFont( KIUI::GetStatusFont( this ) );
137     m_statusLine3->SetFont( KIUI::GetStatusFont( this ) );
138 
139     // Add buttons:
140     auto buttonsSizer = new wxBoxSizer( wxHORIZONTAL );
141     auto sdbSizer = new wxStdDialogButtonSizer();
142 
143     m_saveAndContinue = new wxButton( bottomPanel, wxID_ANY,
144                                       _( "Apply, Save Schematic && Continue" ) );
145     buttonsSizer->Add( m_saveAndContinue, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 20 );
146 
147     auto sdbSizerOK = new wxButton( bottomPanel, wxID_OK );
148     sdbSizer->AddButton( sdbSizerOK );
149     auto sdbSizerCancel = new wxButton( bottomPanel, wxID_CANCEL );
150     sdbSizer->AddButton( sdbSizerCancel );
151     sdbSizer->Realize();
152 
153     buttonsSizer->Add( sdbSizer, 0, 0, 5 );
154     panelSizer->Add( buttonsSizer, 0, wxALIGN_RIGHT|wxALL, 5 );
155 
156     bottomPanel->SetSizer( panelSizer );
157     bottomPanel->Fit();
158 
159     sdbSizerOK->SetDefault();
160     KIPLATFORM::UI::FixupCancelButtonCmdKeyCollision( this );
161 
162     m_auimgr.AddPane( bottomPanel, EDA_PANE().HToolbar().Name( "Buttons" ).Bottom().Layer(6) );
163 
164     m_auimgr.Update();
165     m_initialized = true;
166 
167     if( CVPCB_SETTINGS* cfg = dynamic_cast<CVPCB_SETTINGS*>( config() ) )
168     {
169         if( cfg->m_LibrariesWidth > 0 )
170         {
171             wxAuiPaneInfo& librariesPane = m_auimgr.GetPane( "Libraries" );
172 
173             // wxAUI hack: force width by setting MinSize() and then Fixed()
174             // thanks to ZenJu http://trac.wxwidgets.org/ticket/13180
175             librariesPane.MinSize( cfg->m_LibrariesWidth, -1 );
176             librariesPane.BestSize( cfg->m_LibrariesWidth, -1 );
177             librariesPane.MaxSize( cfg->m_LibrariesWidth, -1 );
178             librariesPane.Fixed();
179             m_auimgr.Update();
180 
181             // now make it resizable again
182             librariesPane.MinSize( 20, -1 );
183             librariesPane.Resizable();
184             m_auimgr.Update();
185         }
186 
187         if( cfg->m_FootprintsWidth > 0 )
188         {
189             wxAuiPaneInfo& footprintsPane = m_auimgr.GetPane( "Footprints" );
190 
191             // wxAUI hack: force width by setting MinSize() and then Fixed()
192             // thanks to ZenJu http://trac.wxwidgets.org/ticket/13180
193             footprintsPane.MinSize( cfg->m_FootprintsWidth, -1 );
194             footprintsPane.BestSize( cfg->m_FootprintsWidth, -1 );
195             footprintsPane.MaxSize( cfg->m_FootprintsWidth, -1 );
196             footprintsPane.Fixed();
197             m_auimgr.Update();
198 
199             // now make it resizable again
200             footprintsPane.MinSize( 20, -1 );
201             footprintsPane.Resizable();
202             m_auimgr.Update();
203         }
204     }
205 
206     // Connect Events
207     setupEventHandlers();
208 
209     // Start the main processing loop
210     m_toolManager->InvokeTool( "cvpcb.Control" );
211 
212     KIPLATFORM::APP::SetShutdownBlockReason( this, _( "Symbol to footprint changes are unsaved" ) );
213 }
214 
215 
~CVPCB_MAINFRAME()216 CVPCB_MAINFRAME::~CVPCB_MAINFRAME()
217 {
218     // Shutdown all running tools
219     if( m_toolManager )
220         m_toolManager->ShutdownAllTools();
221 
222     // Clean up the tool infrastructure
223     delete m_actions;
224     delete m_toolManager;
225     delete m_toolDispatcher;
226 
227     m_auimgr.UnInit();
228 }
229 
230 
setupTools()231 void CVPCB_MAINFRAME::setupTools()
232 {
233     // Create the manager
234     m_actions = new CVPCB_ACTIONS();
235     m_toolManager = new TOOL_MANAGER;
236     m_toolManager->SetEnvironment( nullptr, nullptr, nullptr, config(), this );
237     m_toolDispatcher = new TOOL_DISPATCHER( m_toolManager );
238 
239     // Register tools
240     m_toolManager->RegisterTool( new COMMON_CONTROL );
241     m_toolManager->RegisterTool( new CVPCB_CONTROL );
242     m_toolManager->RegisterTool( new CVPCB_ASSOCIATION_TOOL );
243     m_toolManager->InitTools();
244 
245     CVPCB_CONTROL* tool = m_toolManager->GetTool<CVPCB_CONTROL>();
246 
247     // Even though these menus will open with the right-click, we treat them as a normal
248     // menu instead of a context menu because we don't care about their position and want
249     // to be able to tell the difference between a menu click and a hotkey activation.
250 
251     // Create the context menu for the symbols list box
252     m_symbolsContextMenu = new ACTION_MENU( false, tool );
253     m_symbolsContextMenu->Add( CVPCB_ACTIONS::showFootprintViewer );
254     m_symbolsContextMenu->AppendSeparator();
255     m_symbolsContextMenu->Add( ACTIONS::cut );
256     m_symbolsContextMenu->Add( ACTIONS::copy );
257     m_symbolsContextMenu->Add( ACTIONS::paste );
258     m_symbolsContextMenu->AppendSeparator();
259     m_symbolsContextMenu->Add( CVPCB_ACTIONS::deleteAssoc );
260 
261     // Create the context menu for the footprint list box
262     m_footprintContextMenu = new ACTION_MENU( false, tool );
263     m_footprintContextMenu->Add( CVPCB_ACTIONS::showFootprintViewer );
264 }
265 
266 
setupUIConditions()267 void CVPCB_MAINFRAME::setupUIConditions()
268 {
269     EDA_BASE_FRAME::setupUIConditions();
270 
271     ACTION_MANAGER*   mgr = m_toolManager->GetActionManager();
272     EDITOR_CONDITIONS cond( this );
273 
274     wxASSERT( mgr );
275 
276 #define ENABLE( x ) ACTION_CONDITIONS().Enable( x )
277 #define CHECK( x )  ACTION_CONDITIONS().Check( x )
278 
279     mgr->SetConditions( CVPCB_ACTIONS::saveAssociations, ENABLE( cond.ContentModified() ) );
280     mgr->SetConditions( ACTIONS::undo,                   ENABLE( cond.UndoAvailable() ) );
281     mgr->SetConditions( ACTIONS::redo,                   ENABLE( cond.RedoAvailable() ) );
282 
283     auto compFilter =
284             [this] ( const SELECTION& )
285             {
286                 return m_filteringOptions & FOOTPRINTS_LISTBOX::FILTERING_BY_COMPONENT_FP_FILTERS;
287             };
288 
289     auto libFilter =
290             [this] ( const SELECTION& )
291             {
292                 return m_filteringOptions & FOOTPRINTS_LISTBOX::FILTERING_BY_LIBRARY;
293             };
294 
295     auto pinFilter =
296             [this] ( const SELECTION& )
297             {
298                 return m_filteringOptions & FOOTPRINTS_LISTBOX::FILTERING_BY_PIN_COUNT;
299             };
300 
301     mgr->SetConditions( CVPCB_ACTIONS::FilterFPbyFPFilters, CHECK( compFilter ) );
302     mgr->SetConditions( CVPCB_ACTIONS::FilterFPbyLibrary,   CHECK( libFilter ) );
303     mgr->SetConditions( CVPCB_ACTIONS::filterFPbyPin,       CHECK( pinFilter ) );
304 
305 #undef CHECK
306 #undef ENABLE
307 }
308 
309 
setupEventHandlers()310 void CVPCB_MAINFRAME::setupEventHandlers()
311 {
312     // Connect the handlers to launch the context menus in the listboxes
313     m_footprintListBox->Bind( wxEVT_RIGHT_DOWN,
314             [this]( wxMouseEvent& )
315             {
316                 PopupMenu( m_footprintContextMenu );
317             } );
318 
319     m_symbolsListBox->Bind( wxEVT_RIGHT_DOWN,
320             [this]( wxMouseEvent& )
321             {
322                 PopupMenu( m_symbolsContextMenu );
323             } );
324 
325     // Connect the handler for the save button
326     m_saveAndContinue->Bind( wxEVT_COMMAND_BUTTON_CLICKED,
327             [this]( wxCommandEvent& )
328             {
329                 // saveAssociations must be run immediately
330                 GetToolManager()->RunAction( CVPCB_ACTIONS::saveAssociations, true );
331             } );
332 
333     // Connect the handlers for the ok/cancel buttons
334     Bind( wxEVT_BUTTON,
335             [this]( wxCommandEvent& )
336             {
337                 // saveAssociations must be run immediately, before running Close( true )
338                 GetToolManager()->RunAction( CVPCB_ACTIONS::saveAssociations, true );
339                 Close( true );
340             }, wxID_OK );
341     Bind( wxEVT_BUTTON,
342             [this]( wxCommandEvent& )
343             {
344                 Close( false );
345             }, wxID_CANCEL );
346 
347     // Connect the handlers for the close events
348     Bind( wxEVT_MENU,
349             [this]( wxCommandEvent& )
350             {
351                 Close( false );
352             }, wxID_CLOSE );
353     Bind( wxEVT_MENU,
354             [this]( wxCommandEvent& )
355             {
356                 Close( false );
357             }, wxID_EXIT );
358 
359     // Toolbar events
360     Bind( wxEVT_TEXT, &CVPCB_MAINFRAME::OnEnterFilteringText, this, ID_CVPCB_FILTER_TEXT_EDIT );
361 
362     // Just skip the resize events
363     Bind( wxEVT_SIZE,
364           []( wxSizeEvent& aEvent )
365           {
366               aEvent.Skip();
367           } );
368 
369     // Attach the events to the tool dispatcher
370     Bind( wxEVT_CHAR, &TOOL_DISPATCHER::DispatchWxEvent, m_toolDispatcher );
371     Bind( wxEVT_CHAR_HOOK, &TOOL_DISPATCHER::DispatchWxEvent, m_toolDispatcher );
372 }
373 
374 
canCloseWindow(wxCloseEvent & aEvent)375 bool CVPCB_MAINFRAME::canCloseWindow( wxCloseEvent& aEvent )
376 {
377     if( m_modified )
378     {
379         // Shutdown blocks must be determined and vetoed as early as possible
380         if( KIPLATFORM::APP::SupportsShutdownBlockReason()
381                 && aEvent.GetId() == wxEVT_QUERY_END_SESSION )
382         {
383             return false;
384         }
385 
386         if( !HandleUnsavedChanges( this, _( "Symbol to Footprint links have been modified. "
387                                             "Save changes?" ),
388                                    [&]() -> bool
389                                    {
390                                        return SaveFootprintAssociation( false );
391                                    } ) )
392         {
393             return false;
394         }
395     }
396 
397     if( m_cannotClose )
398         return false;
399 
400     return true;
401 }
402 
403 
doCloseWindow()404 void CVPCB_MAINFRAME::doCloseWindow()
405 {
406     if( GetFootprintViewerFrame() )
407         GetFootprintViewerFrame()->Close( true );
408 
409     m_modified = false;
410 
411     // clear highlight symbol in schematic:
412     SendMessageToEESCHEMA( true );
413 }
414 
415 
OnEnterFilteringText(wxCommandEvent & aEvent)416 void CVPCB_MAINFRAME::OnEnterFilteringText( wxCommandEvent& aEvent )
417 {
418     // Called when changing the filter string in main toolbar.
419     // If the option FOOTPRINTS_LISTBOX::FILTERING_BY_TEXT_PATTERN is set, update the list
420     // of available footprints which match the filter
421 
422     wxListEvent l_event;
423     OnSelectComponent( l_event );
424 }
425 
426 
OnSelectComponent(wxListEvent & event)427 void CVPCB_MAINFRAME::OnSelectComponent( wxListEvent& event )
428 {
429     if( m_skipComponentSelect )
430         return;
431 
432     wxString   libraryName;
433     COMPONENT* symbol = GetSelectedComponent();
434     libraryName = m_librariesListBox->GetSelectedLibrary();
435 
436     m_footprintListBox->SetFootprints( *m_FootprintsList, libraryName, symbol,
437                                        m_tcFilterString->GetValue(), m_filteringOptions );
438 
439     if( symbol && symbol->GetFPID().IsValid() )
440         m_footprintListBox->SetSelectedFootprint( symbol->GetFPID() );
441     else
442         m_footprintListBox->SetSelection( m_footprintListBox->GetSelection(), false );
443 
444     refreshAfterSymbolSearch( symbol );
445 }
446 
447 
LoadSettings(APP_SETTINGS_BASE * aCfg)448 void CVPCB_MAINFRAME::LoadSettings( APP_SETTINGS_BASE* aCfg )
449 {
450     EDA_BASE_FRAME::LoadSettings( aCfg );
451 
452     CVPCB_SETTINGS* cfg = static_cast<CVPCB_SETTINGS*>( aCfg );
453 
454     m_filteringOptions = cfg->m_FilterFootprint;
455 }
456 
457 
SaveSettings(APP_SETTINGS_BASE * aCfg)458 void CVPCB_MAINFRAME::SaveSettings( APP_SETTINGS_BASE* aCfg )
459 {
460     EDA_BASE_FRAME::SaveSettings( aCfg );
461 
462     CVPCB_SETTINGS* cfg = static_cast<CVPCB_SETTINGS*>( aCfg );
463     cfg->m_FilterFootprint = m_filteringOptions;
464 
465     cfg->m_LibrariesWidth = m_librariesListBox->GetSize().x;
466     cfg->m_FootprintsWidth = m_footprintListBox->GetSize().x;
467 }
468 
469 
UndoAssociation()470 void CVPCB_MAINFRAME::UndoAssociation()
471 {
472     if( m_undoList.size() == 0 )
473         return;
474 
475     CVPCB_UNDO_REDO_ENTRIES redoEntries;
476     CVPCB_UNDO_REDO_ENTRIES curEntry = m_undoList.back();
477     m_undoList.pop_back();
478 
479     // Iterate over the entries to undo
480     for( const auto& assoc : curEntry )
481     {
482         AssociateFootprint( assoc, true, false );
483         redoEntries.emplace_back( assoc.Reverse() );
484     }
485 
486     // Add the redo entries to the redo stack
487     m_redoList.emplace_back( redoEntries );
488 }
489 
490 
RedoAssociation()491 void CVPCB_MAINFRAME::RedoAssociation()
492 {
493     if( m_redoList.size() == 0 )
494         return;
495 
496     CVPCB_UNDO_REDO_ENTRIES curEntry = m_redoList.back();
497     m_redoList.pop_back();
498 
499     // Iterate over the entries to undo
500     bool firstAssoc = true;
501 
502     for( const auto& assoc : curEntry )
503     {
504         AssociateFootprint( assoc, firstAssoc );
505         firstAssoc = false;
506     }
507 }
508 
509 
formatSymbolDesc(int idx,const wxString & aReference,const wxString & aValue,const wxString & aFootprint)510 wxString CVPCB_MAINFRAME::formatSymbolDesc( int idx, const wxString& aReference,
511                                             const wxString& aValue, const wxString& aFootprint )
512 {
513     // Work around a bug in wxString::Format with double-byte chars (and double-quote, for some
514     // reason).
515     wxString desc = wxString::Format( wxT( "%3d " ), idx );
516 
517     for( int ii = aReference.Length(); ii < 8; ++ii )
518         desc += wxS( " " );
519 
520     desc += aReference + wxT( " - " );
521 
522     for( int ii = aValue.Length(); ii < 16; ++ii )
523         desc += wxS( " " );
524 
525     desc += aValue + wxT( " : " ) + aFootprint;
526 
527     return desc;
528 }
529 
530 
AssociateFootprint(const CVPCB_ASSOCIATION & aAssociation,bool aNewEntry,bool aAddUndoItem)531 void CVPCB_MAINFRAME::AssociateFootprint( const CVPCB_ASSOCIATION& aAssociation,
532                                           bool aNewEntry, bool aAddUndoItem )
533 {
534     if( m_netlist.IsEmpty() )
535         return;
536 
537     COMPONENT* symbol = m_netlist.GetComponent( aAssociation.GetComponentIndex() );
538 
539     if( symbol == nullptr )
540         return;
541 
542     LIB_ID fpid    = aAssociation.GetNewFootprint();
543     LIB_ID oldFpid = symbol->GetFPID();
544 
545     // Test for validity of the requested footprint
546     if( !fpid.empty() && !fpid.IsValid() )
547     {
548         wxString msg = wxString::Format( _( "'%s' is not a valid footprint." ),
549                                          fpid.Format().wx_str() );
550         DisplayErrorMessage( this, msg );
551         return;
552     }
553 
554     const KIID& id = symbol->GetKIIDs().front();
555 
556     // Set new footprint to all instances of the selected symbol
557     for( unsigned int idx : GetComponentIndices() )
558     {
559         COMPONENT* candidate = m_netlist.GetComponent( idx );
560         const std::vector<KIID>& kiids = candidate->GetKIIDs();
561 
562         if( std::find( kiids.begin(), kiids.end(), id ) != kiids.end() )
563         {
564             // Set the new footprint
565             candidate->SetFPID( fpid );
566 
567             // create the new symbol description and set it
568             wxString description = formatSymbolDesc( idx + 1,
569                                                      candidate->GetReference(),
570                                                      candidate->GetValue(),
571                                                      candidate->GetFPID().Format().wx_str() );
572             m_symbolsListBox->SetString( idx, description );
573         }
574     }
575 
576     // Mark the data as being modified
577     m_modified = true;
578 
579     // Update the statusbar and refresh the list
580     DisplayStatus();
581     m_symbolsListBox->Refresh();
582 
583     if( !aAddUndoItem )
584         return;
585 
586     // Update the undo list
587     if ( aNewEntry )
588     {
589         // Create a new entry for this association
590         CVPCB_UNDO_REDO_ENTRIES newEntry;
591         newEntry.emplace_back( CVPCB_ASSOCIATION( aAssociation.GetComponentIndex(), oldFpid,
592                                                   aAssociation.GetNewFootprint() ) );
593         m_undoList.emplace_back( newEntry );
594 
595         // Clear the redo list
596         m_redoList.clear();
597     }
598     else
599     {
600         m_undoList.back().emplace_back( CVPCB_ASSOCIATION( aAssociation.GetComponentIndex(),
601                                                            oldFpid,
602                                                            aAssociation.GetNewFootprint() ) );
603     }
604 
605 }
606 
607 
OpenProjectFiles(const std::vector<wxString> & aFileSet,int aCtl)608 bool CVPCB_MAINFRAME::OpenProjectFiles( const std::vector<wxString>& aFileSet, int aCtl )
609 {
610     return true;
611 }
612 
613 
refreshAfterSymbolSearch(COMPONENT * aSymbol)614 void CVPCB_MAINFRAME::refreshAfterSymbolSearch( COMPONENT* aSymbol )
615 {
616     // Tell AuiMgr that objects are changed !
617     if( m_auimgr.GetManagedWindow() )   // Be sure Aui Manager is initialized
618         m_auimgr.Update();              // (could be not the case when starting CvPcb)
619 
620     if( aSymbol == nullptr )
621     {
622         DisplayStatus();
623         return;
624     }
625 
626     // Preview of the already assigned footprint.
627     // Find the footprint that was already chosen for this aSymbol and select it,
628     // but only if the selection is made from the aSymbol list or the library list.
629     // If the selection is made from the footprint list, do not change the current
630     // selected footprint.
631     if( FindFocus() == m_symbolsListBox || FindFocus() == m_librariesListBox )
632     {
633         wxString footprintName = FROM_UTF8( aSymbol->GetFPID().Format().c_str() );
634 
635         m_footprintListBox->SetSelection( m_footprintListBox->GetSelection(), false );
636 
637         for( int ii = 0; ii < m_footprintListBox->GetCount(); ii++ )
638         {
639             wxString candidateName;
640             wxString msg = m_footprintListBox->OnGetItemText( ii, 0 );
641             msg.Trim( true );
642             msg.Trim( false );
643             candidateName = msg.AfterFirst( wxChar( ' ' ) );
644 
645             if( footprintName.Cmp( candidateName ) == 0 )
646             {
647                 m_footprintListBox->SetSelection( ii, true );
648                 break;
649             }
650         }
651 
652         if( GetFootprintViewerFrame() )
653             m_toolManager->RunAction( CVPCB_ACTIONS::showFootprintViewer, true );
654     }
655 
656     SendMessageToEESCHEMA();
657     DisplayStatus();
658 }
659 
660 
SetFootprintFilter(FOOTPRINTS_LISTBOX::FP_FILTER_T aFilter,CVPCB_MAINFRAME::CVPCB_FILTER_ACTION aAction)661 void CVPCB_MAINFRAME::SetFootprintFilter( FOOTPRINTS_LISTBOX::FP_FILTER_T aFilter,
662                                           CVPCB_MAINFRAME::CVPCB_FILTER_ACTION aAction )
663 {
664     int option = aFilter;
665 
666     // Apply the filter accordingly
667     switch( aAction )
668     {
669     case CVPCB_MAINFRAME::FILTER_DISABLE: m_filteringOptions &= ~option; break;
670     case CVPCB_MAINFRAME::FILTER_ENABLE:  m_filteringOptions |= option;  break;
671     case CVPCB_MAINFRAME::FILTER_TOGGLE:  m_filteringOptions ^= option;  break;
672     }
673 
674     wxListEvent l_event;
675     OnSelectComponent( l_event );
676 }
677 
678 
DisplayStatus()679 void CVPCB_MAINFRAME::DisplayStatus()
680 {
681     if( !m_initialized )
682         return;
683 
684     wxString   filters, msg;
685     COMPONENT* symbol = GetSelectedComponent();
686 
687     if( ( m_filteringOptions & FOOTPRINTS_LISTBOX::FILTERING_BY_COMPONENT_FP_FILTERS ) )
688     {
689         msg.Empty();
690 
691         if( symbol )
692         {
693             for( unsigned ii = 0; ii < symbol->GetFootprintFilters().GetCount(); ii++ )
694             {
695                 if( msg.IsEmpty() )
696                     msg += symbol->GetFootprintFilters()[ii];
697                 else
698                     msg += wxT( ", " ) + symbol->GetFootprintFilters()[ii];
699             }
700         }
701 
702         filters += _( "Keywords" );
703 
704         if( !msg.IsEmpty() )
705             filters += wxString::Format( wxT( " (%s)" ), msg );
706     }
707 
708     if( ( m_filteringOptions & FOOTPRINTS_LISTBOX::FILTERING_BY_PIN_COUNT ) )
709     {
710         msg.Empty();
711 
712         if( symbol )
713             msg = wxString::Format( wxT( "%i" ), symbol->GetPinCount() );
714 
715         if( !filters.IsEmpty() )
716             filters += wxT( ", " );
717 
718         filters += _( "Pin Count" );
719 
720         if( !msg.IsEmpty() )
721             filters += wxString::Format( wxT( " (%s)" ), msg );
722     }
723 
724     if( ( m_filteringOptions & FOOTPRINTS_LISTBOX::FILTERING_BY_LIBRARY ) )
725     {
726         msg = m_librariesListBox->GetSelectedLibrary();
727 
728         if( !filters.IsEmpty() )
729             filters += wxT( ", " );
730 
731         filters += _( "Library" );
732 
733         if( !msg.IsEmpty() )
734             filters += wxString::Format( wxT( " (%s)" ), msg );
735     }
736 
737     wxString textFilter = m_tcFilterString->GetValue();
738 
739     if( !textFilter.IsEmpty() )
740     {
741         if( !filters.IsEmpty() )
742             filters += wxT( ", " );
743 
744         filters += _( "Search Text" ) + wxString::Format( wxT( " (%s)" ), textFilter );
745     }
746 
747     if( filters.IsEmpty() )
748         msg = _( "No Filtering" );
749     else
750         msg.Printf( _( "Filtered by %s" ), filters );
751 
752     msg << wxT( ": " ) << m_footprintListBox->GetCount();
753 
754     SetStatusText( msg );
755 
756 
757     msg.Empty();
758     wxString footprintName = GetSelectedFootprint();
759 
760     FOOTPRINT_INFO* fp = m_FootprintsList->GetFootprintInfo( footprintName );
761 
762     if( fp )    // can be NULL if no netlist loaded
763     {
764         msg = wxString::Format( _( "Description: %s;  Keywords: %s" ),
765                                 fp->GetDescription(),
766                                 fp->GetKeywords() );
767     }
768 
769     SetStatusText( msg, 1 );
770 
771     msg.Empty();
772     wxString lib;
773 
774     // Choose the footprint to get the information on
775     if( fp )
776     {
777         // Use the footprint in the footprint viewer
778         lib = fp->GetLibNickname();
779     }
780     else if( GetFocusedControl() == CVPCB_MAINFRAME::CONTROL_COMPONENT )
781     {
782         // Use the footprint of the selected symbol
783         if( symbol )
784             lib = symbol->GetFPID().GetLibNickname();
785     }
786     else if( GetFocusedControl() == CVPCB_MAINFRAME::CONTROL_LIBRARY )
787     {
788         // Use the library that is selected
789         lib = m_librariesListBox->GetSelectedLibrary();
790     }
791 
792     // Extract the library information
793     FP_LIB_TABLE* fptbl = Prj().PcbFootprintLibs();
794 
795     if( fptbl->HasLibrary( lib ) )
796         msg = wxString::Format( _( "Library location: %s" ), fptbl->GetFullURI( lib ) );
797     else
798         msg = wxString::Format( _( "Library location: unknown" ) );
799 
800     SetStatusText( msg, 2 );
801 }
802 
803 
LoadFootprintFiles()804 bool CVPCB_MAINFRAME::LoadFootprintFiles()
805 {
806     FP_LIB_TABLE* fptbl = Prj().PcbFootprintLibs();
807 
808     // Check if there are footprint libraries in the footprint library table.
809     if( !fptbl || !fptbl->GetLogicalLibs().size() )
810     {
811         wxMessageBox( _( "No PCB footprint libraries are listed in the current footprint "
812                          "library table." ), _( "Configuration Error" ), wxOK | wxICON_ERROR );
813         return false;
814     }
815 
816     WX_PROGRESS_REPORTER progressReporter( this, _( "Loading Footprint Libraries" ), 2 );
817 
818     m_FootprintsList->ReadFootprintFiles( fptbl, nullptr, &progressReporter );
819 
820     if( m_FootprintsList->GetErrorCount() )
821     {
822         m_FootprintsList->DisplayErrors( this );
823     }
824 
825     return true;
826 }
827 
828 
SendMessageToEESCHEMA(bool aClearHighligntOnly)829 void CVPCB_MAINFRAME::SendMessageToEESCHEMA( bool aClearHighligntOnly )
830 {
831     if( m_netlist.IsEmpty() )
832         return;
833 
834     // clear highlight of previously selected symbols (if any):
835     // Selecting a non existing symbol clears any previously highlighted symbols
836     std::string packet = "$CLEAR: \"HIGHLIGHTED\"";
837 
838     if( Kiface().IsSingle() )
839         SendCommand( MSG_TO_SCH, packet );
840     else
841         Kiway().ExpressMail( FRAME_SCH, MAIL_CROSS_PROBE, packet, this );
842 
843     if( aClearHighligntOnly )
844         return;
845 
846     int selection = m_symbolsListBox->GetSelection();
847 
848     if ( selection < 0 )    // Nothing selected
849         return;
850 
851     if( m_netlist.GetComponent( selection ) == nullptr )
852         return;
853 
854     // Now highlight the selected symbol:
855     COMPONENT* symbol = m_netlist.GetComponent( selection );
856 
857     packet = std::string( "$PART: \"" ) + TO_UTF8( symbol->GetReference() ) + "\"";
858 
859     if( Kiface().IsSingle() )
860         SendCommand( MSG_TO_SCH, packet );
861     else
862         Kiway().ExpressMail( FRAME_SCH, MAIL_CROSS_PROBE, packet, this );
863 }
864 
865 
ReadSchematicNetlist(const std::string & aNetlist)866 int CVPCB_MAINFRAME::ReadSchematicNetlist( const std::string& aNetlist )
867 {
868     STRING_LINE_READER*  stringReader = new STRING_LINE_READER( aNetlist, "Eeschema via Kiway" );
869     KICAD_NETLIST_READER netlistReader( stringReader, &m_netlist );
870 
871     m_netlist.Clear();
872 
873     try
874     {
875         netlistReader.LoadNetlist();
876     }
877     catch( const IO_ERROR& ioe )
878     {
879         wxString msg = wxString::Format( _( "Error loading schematic.\n%s" ),
880                                          ioe.What().GetData() );
881         wxMessageBox( msg, _( "Load Error" ), wxOK | wxICON_ERROR );
882         return 1;
883     }
884 
885     // We also remove footprint name if it is "$noname" because this is a dummy name,
886     // not the actual name of the footprint.
887     for( unsigned ii = 0; ii < m_netlist.GetCount(); ii++ )
888     {
889         if( m_netlist.GetComponent( ii )->GetFPID().GetLibItemName() == std::string( "$noname" ) )
890             m_netlist.GetComponent( ii )->SetFPID( LIB_ID() );
891     }
892 
893     // Sort symbols by reference:
894     m_netlist.SortByReference();
895 
896     return 0;
897 }
898 
899 
BuildFootprintsListBox()900 void CVPCB_MAINFRAME::BuildFootprintsListBox()
901 {
902     wxFont   guiFont = wxSystemSettings::GetFont( wxSYS_DEFAULT_GUI_FONT );
903 
904     if( m_footprintListBox == nullptr )
905     {
906         m_footprintListBox = new FOOTPRINTS_LISTBOX( this, ID_CVPCB_FOOTPRINT_LIST );
907         m_footprintListBox->SetFont( KIUI::GetMonospacedUIFont() );
908     }
909 
910     m_footprintListBox->SetFootprints( *m_FootprintsList, wxEmptyString, nullptr, wxEmptyString,
911                                        FOOTPRINTS_LISTBOX::UNFILTERED_FP_LIST );
912     DisplayStatus();
913 }
914 
915 
BuildSymbolsListBox()916 void CVPCB_MAINFRAME::BuildSymbolsListBox()
917 {
918     wxString    msg;
919     COMPONENT*  symbol;
920     wxFont      guiFont = wxSystemSettings::GetFont( wxSYS_DEFAULT_GUI_FONT );
921 
922     if( m_symbolsListBox == nullptr )
923     {
924         m_symbolsListBox = new SYMBOLS_LISTBOX( this, ID_CVPCB_COMPONENT_LIST );
925         m_symbolsListBox->SetFont( KIUI::GetMonospacedUIFont() );
926     }
927 
928     m_symbolsListBox->m_SymbolList.Clear();
929 
930     for( unsigned i = 0;  i < m_netlist.GetCount();  i++ )
931     {
932         symbol = m_netlist.GetComponent( i );
933 
934         msg = formatSymbolDesc( m_symbolsListBox->GetCount() + 1,
935                                 symbol->GetReference(),
936                                 symbol->GetValue(),
937                                 symbol->GetFPID().Format().wx_str() );
938         m_symbolsListBox->m_SymbolList.Add( msg );
939     }
940 
941     if( m_symbolsListBox->m_SymbolList.Count() )
942     {
943         m_symbolsListBox->SetItemCount( m_symbolsListBox->m_SymbolList.Count() );
944         m_symbolsListBox->SetSelection( 0, true );
945         m_symbolsListBox->RefreshItems( 0L, m_symbolsListBox->m_SymbolList.Count() - 1 );
946         m_symbolsListBox->UpdateWidth();
947     }
948 }
949 
950 
BuildLibrariesListBox()951 void CVPCB_MAINFRAME::BuildLibrariesListBox()
952 {
953     wxFont   guiFont = wxSystemSettings::GetFont( wxSYS_DEFAULT_GUI_FONT );
954 
955     if( m_librariesListBox == nullptr )
956     {
957         m_librariesListBox = new LIBRARY_LISTBOX( this, ID_CVPCB_LIBRARY_LIST );
958         m_librariesListBox->SetFont( KIUI::GetMonospacedUIFont() );
959     }
960 
961     FP_LIB_TABLE* tbl = Prj().PcbFootprintLibs();
962 
963     if( tbl )
964     {
965         wxArrayString libNames;
966 
967         std::vector< wxString > libNickNames = tbl->GetLogicalLibs();
968 
969         for( const wxString& libNickName : libNickNames )
970             libNames.Add( libNickName );
971 
972         m_librariesListBox->SetLibraryList( libNames );
973     }
974 }
975 
976 
GetSelectedComponent()977 COMPONENT* CVPCB_MAINFRAME::GetSelectedComponent()
978 {
979     int selection = m_symbolsListBox->GetSelection();
980 
981     if( selection >= 0 && selection < (int) m_netlist.GetCount() )
982         return m_netlist.GetComponent( selection );
983 
984     return nullptr;
985 }
986 
987 
SetSelectedComponent(int aIndex,bool aSkipUpdate)988 void CVPCB_MAINFRAME::SetSelectedComponent( int aIndex, bool aSkipUpdate )
989 {
990     m_skipComponentSelect = aSkipUpdate;
991 
992     if( aIndex < 0 )
993     {
994         m_symbolsListBox->DeselectAll();
995     }
996     else if( aIndex < m_symbolsListBox->GetCount() )
997     {
998         m_symbolsListBox->DeselectAll();
999         m_symbolsListBox->SetSelection( aIndex );
1000         SendMessageToEESCHEMA();
1001     }
1002 
1003     m_skipComponentSelect = false;
1004 }
1005 
1006 
GetComponentIndices(CVPCB_MAINFRAME::CRITERIA aCriteria)1007 std::vector<unsigned int> CVPCB_MAINFRAME::GetComponentIndices(
1008         CVPCB_MAINFRAME::CRITERIA aCriteria )
1009 {
1010     std::vector<unsigned int> idx;
1011     int                       lastIdx;
1012 
1013     // Make sure a netlist has been loaded and the box has contents
1014     if( m_netlist.IsEmpty() || m_symbolsListBox->GetCount() == 0 )
1015         return idx;
1016 
1017     switch( aCriteria )
1018     {
1019     case CVPCB_MAINFRAME::ALL_COMPONENTS:
1020         idx.resize( m_netlist.GetCount() );
1021         std::iota( idx.begin(), idx.end(), 0 );
1022         break;
1023 
1024     case CVPCB_MAINFRAME::SEL_COMPONENTS:
1025         // Check to see if anything is selected
1026         if( m_symbolsListBox->GetSelectedItemCount() < 1 )
1027             break;
1028 
1029         // Get the symbols
1030         lastIdx = m_symbolsListBox->GetFirstSelected();
1031         idx.emplace_back( lastIdx );
1032 
1033         lastIdx = m_symbolsListBox->GetNextSelected( lastIdx );
1034         while( lastIdx > 0 )
1035         {
1036             idx.emplace_back( lastIdx );
1037             lastIdx = m_symbolsListBox->GetNextSelected( lastIdx );
1038         }
1039         break;
1040 
1041     case CVPCB_MAINFRAME::NA_COMPONENTS:
1042         for( unsigned int i = 0; i < m_netlist.GetCount(); i++ )
1043         {
1044             if( m_netlist.GetComponent( i )->GetFPID().empty() )
1045                 idx.emplace_back( i );
1046         }
1047         break;
1048 
1049     case CVPCB_MAINFRAME::ASSOC_COMPONENTS:
1050         for( unsigned int i = 0; i < m_netlist.GetCount(); i++ )
1051         {
1052             if( !m_netlist.GetComponent( i )->GetFPID().empty() )
1053                 idx.emplace_back( i );
1054         }
1055         break;
1056 
1057     default:
1058         wxASSERT_MSG( false, "Invalid symbol selection criteria" );
1059     }
1060 
1061     return idx;
1062 }
1063 
1064 
GetFootprintViewerFrame() const1065 DISPLAY_FOOTPRINTS_FRAME* CVPCB_MAINFRAME::GetFootprintViewerFrame() const
1066 {
1067     // returns the Footprint Viewer frame, if exists, or NULL
1068     wxWindow* window = wxWindow::FindWindowByName( FOOTPRINTVIEWER_FRAME_NAME );
1069     return dynamic_cast<DISPLAY_FOOTPRINTS_FRAME*>( window );
1070 }
1071 
1072 
GetToolCanvas() const1073 wxWindow* CVPCB_MAINFRAME::GetToolCanvas() const
1074 {
1075     return GetFootprintViewerFrame();
1076 }
1077 
1078 
GetFocusedControl() const1079 CVPCB_MAINFRAME::CONTROL_TYPE CVPCB_MAINFRAME::GetFocusedControl() const
1080 {
1081     if( m_librariesListBox->HasFocus() )
1082         return CVPCB_MAINFRAME::CONTROL_LIBRARY;
1083     else if( m_symbolsListBox->HasFocus() )
1084         return CVPCB_MAINFRAME::CONTROL_COMPONENT;
1085     else if( m_footprintListBox->HasFocus() )
1086         return CVPCB_MAINFRAME::CONTROL_FOOTPRINT;
1087 
1088     return CVPCB_MAINFRAME::CONTROL_NONE;
1089 }
1090 
1091 
GetFocusedControlObject() const1092 wxControl* CVPCB_MAINFRAME::GetFocusedControlObject() const
1093 {
1094     if( m_librariesListBox->HasFocus() )
1095         return m_librariesListBox;
1096     else if( m_symbolsListBox->HasFocus() )
1097         return m_symbolsListBox;
1098     else if( m_footprintListBox->HasFocus() )
1099         return m_footprintListBox;
1100 
1101     return nullptr;
1102 }
1103 
1104 
SetFocusedControl(CVPCB_MAINFRAME::CONTROL_TYPE aLB)1105 void CVPCB_MAINFRAME::SetFocusedControl( CVPCB_MAINFRAME::CONTROL_TYPE aLB )
1106 {
1107     switch( aLB )
1108     {
1109     case CVPCB_MAINFRAME::CONTROL_LIBRARY:   m_librariesListBox->SetFocus(); break;
1110     case CVPCB_MAINFRAME::CONTROL_COMPONENT: m_symbolsListBox->SetFocus();   break;
1111     case CVPCB_MAINFRAME::CONTROL_FOOTPRINT: m_footprintListBox->SetFocus(); break;
1112     default:                                                                 break;
1113     }
1114 }
1115 
1116 
GetSelectedFootprint()1117 wxString CVPCB_MAINFRAME::GetSelectedFootprint()
1118 {
1119     // returns the LIB_ID of the selected footprint in footprint listview
1120     // or a empty string
1121     return m_footprintListBox->GetSelectedFootprint();
1122 }
1123 
1124 
SetStatusText(const wxString & aText,int aNumber)1125 void CVPCB_MAINFRAME::SetStatusText( const wxString& aText, int aNumber )
1126 {
1127     switch( aNumber )
1128     {
1129     case 0:  m_statusLine1->SetLabel( aText );          break;
1130     case 1:  m_statusLine2->SetLabel( aText );          break;
1131     case 2:  m_statusLine3->SetLabel( aText );          break;
1132     default: wxFAIL_MSG( "Invalid status row number" ); break;
1133     }
1134 }
1135 
1136 
ShowChangedLanguage()1137 void CVPCB_MAINFRAME::ShowChangedLanguage()
1138 {
1139     EDA_BASE_FRAME::ShowChangedLanguage();
1140     ReCreateHToolbar();
1141     DisplayStatus();
1142 }
1143 
1144 
KiwayMailIn(KIWAY_EXPRESS & mail)1145 void CVPCB_MAINFRAME::KiwayMailIn( KIWAY_EXPRESS& mail )
1146 {
1147     const std::string& payload = mail.GetPayload();
1148 
1149     switch( mail.Command() )
1150     {
1151     case MAIL_EESCHEMA_NETLIST:
1152         // Disable Close events during ReadNetListAndFpFiles() to avoid crash when updating
1153         // widgets:
1154         m_cannotClose = true;
1155         ReadNetListAndFpFiles( payload );
1156         m_cannotClose = false;
1157         /* @todo
1158         Go into SCH_EDIT_FRAME::OnOpenCvpcb( wxCommandEvent& event ) and trim GNL_ALL down.
1159         */
1160         break;
1161 
1162     default:
1163         ;       // ignore most
1164     }
1165 }
1166