1 /*
2  * This program source code file is part of KiCad, a free EDA CAD application.
3  *
4  * Copyright (C) 2014 CERN
5  * Copyright (C) 2014-2021 KiCad Developers, see AUTHORS.txt for contributors.
6  * @author Maciej Suminski <maciej.suminski@cern.ch>
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 <functional>
27 #include <memory>
28 
29 #include <advanced_config.h>
30 #include "board_editor_control.h"
31 #include <bitmaps.h>
32 #include <board.h>
33 #include <board_commit.h>
34 #include <board_design_settings.h>
35 #include <pcb_group.h>
36 #include <footprint.h>
37 #include <pad.h>
38 #include <pcb_target.h>
39 #include <pcb_track.h>
40 #include <zone.h>
41 #include <pcb_marker.h>
42 #include <confirm.h>
43 #include <dialogs/dialog_page_settings.h>
44 #include <dialogs/dialog_update_pcb.h>
45 #include <kiface_base.h>
46 #include <kiway.h>
47 #include <netlist_reader/pcb_netlist.h>
48 #include <origin_viewitem.h>
49 #include <pcb_edit_frame.h>
50 #include <pcbnew_id.h>
51 #include <pcbnew_settings.h>
52 #include <project.h>
53 #include <project/project_file.h> // LAST_PATH_TYPE
54 #include <tool/tool_manager.h>
55 #include <tool/tool_event.h>
56 #include <tools/drawing_tool.h>
57 #include <tools/pcb_actions.h>
58 #include <tools/pcb_picker_tool.h>
59 #include <tools/pcb_selection_tool.h>
60 #include <tools/edit_tool.h>
61 #include <tools/tool_event_utils.h>
62 #include <router/router_tool.h>
63 #include <view/view_controls.h>
64 #include <view/view_group.h>
65 #include <wildcards_and_files_ext.h>
66 #include <drawing_sheet/ds_proxy_undo_item.h>
67 #include <footprint_edit_frame.h>
68 #include <wx/filedlg.h>
69 #include <wx/log.h>
70 
71 using namespace std::placeholders;
72 
73 
74 class ZONE_CONTEXT_MENU : public ACTION_MENU
75 {
76 public:
ZONE_CONTEXT_MENU()77     ZONE_CONTEXT_MENU() :
78         ACTION_MENU( true )
79     {
80         SetIcon( BITMAPS::add_zone );
81         SetTitle( _( "Zones" ) );
82 
83         Add( PCB_ACTIONS::zoneFill );
84         Add( PCB_ACTIONS::zoneFillAll );
85         Add( PCB_ACTIONS::zoneUnfill );
86         Add( PCB_ACTIONS::zoneUnfillAll );
87 
88         AppendSeparator();
89 
90         Add( PCB_ACTIONS::zoneMerge );
91         Add( PCB_ACTIONS::zoneDuplicate );
92         Add( PCB_ACTIONS::drawZoneCutout );
93         Add( PCB_ACTIONS::drawSimilarZone );
94     }
95 
96 
97 protected:
create() const98     ACTION_MENU* create() const override
99     {
100         return new ZONE_CONTEXT_MENU();
101     }
102 };
103 
104 
105 class LOCK_CONTEXT_MENU : public ACTION_MENU
106 {
107 public:
LOCK_CONTEXT_MENU()108     LOCK_CONTEXT_MENU() :
109         ACTION_MENU( true )
110     {
111         SetIcon( BITMAPS::locked );
112         SetTitle( _( "Locking" ) );
113 
114         Add( PCB_ACTIONS::lock );
115         Add( PCB_ACTIONS::unlock );
116         Add( PCB_ACTIONS::toggleLock );
117     }
118 
create() const119     ACTION_MENU* create() const override
120     {
121         return new LOCK_CONTEXT_MENU();
122     }
123 };
124 
125 
126 /**
127  * Helper widget to add controls to a wxFileDialog to set netlist configuration options.
128  */
129 class NETLIST_OPTIONS_HELPER : public wxPanel
130 {
131 public:
NETLIST_OPTIONS_HELPER(wxWindow * aParent)132     NETLIST_OPTIONS_HELPER( wxWindow* aParent )
133             : wxPanel( aParent )
134     {
135         m_cbOmitExtras = new wxCheckBox( this, wxID_ANY, _( "Omit extra information" ) );
136         m_cbOmitNets = new wxCheckBox( this, wxID_ANY, _( "Omit nets" ) );
137         m_cbOmitFpUuids = new wxCheckBox( this, wxID_ANY,
138                                           _( "Do not prefix path with footprint UUID." ) );
139 
140         wxBoxSizer* sizer = new wxBoxSizer( wxHORIZONTAL );
141         sizer->Add( m_cbOmitExtras, 0, wxALL, 5 );
142         sizer->Add( m_cbOmitNets, 0, wxALL, 5 );
143         sizer->Add( m_cbOmitFpUuids, 0, wxALL, 5 );
144 
145         SetSizerAndFit( sizer );
146     }
147 
GetNetlistOptions() const148     int GetNetlistOptions() const
149     {
150         int options = 0;
151 
152         if( m_cbOmitExtras->GetValue() )
153             options |= CTL_OMIT_EXTRA;
154 
155         if( m_cbOmitNets->GetValue() )
156             options |= CTL_OMIT_NETS;
157 
158         if( m_cbOmitFpUuids->GetValue() )
159             options |= CTL_OMIT_FP_UUID;
160 
161         return options;
162     }
163 
Create(wxWindow * aParent)164     static wxWindow* Create( wxWindow* aParent )
165     {
166         return new NETLIST_OPTIONS_HELPER( aParent );
167     }
168 
169 protected:
170     wxCheckBox* m_cbOmitExtras;
171     wxCheckBox* m_cbOmitNets;
172     wxCheckBox* m_cbOmitFpUuids;
173 };
174 
175 
BOARD_EDITOR_CONTROL()176 BOARD_EDITOR_CONTROL::BOARD_EDITOR_CONTROL() :
177     PCB_TOOL_BASE( "pcbnew.EditorControl" ),
178     m_frame( nullptr ),
179     m_inPlaceFootprint( false ),
180     m_placingFootprint( false ),
181     m_inPlaceTarget( false )
182 {
183     m_placeOrigin = std::make_unique<KIGFX::ORIGIN_VIEWITEM>( KIGFX::COLOR4D( 0.8, 0.0, 0.0, 1.0 ),
184                                                              KIGFX::ORIGIN_VIEWITEM::CIRCLE_CROSS );
185 }
186 
187 
~BOARD_EDITOR_CONTROL()188 BOARD_EDITOR_CONTROL::~BOARD_EDITOR_CONTROL()
189 {
190 }
191 
192 
Reset(RESET_REASON aReason)193 void BOARD_EDITOR_CONTROL::Reset( RESET_REASON aReason )
194 {
195     m_frame = getEditFrame<PCB_EDIT_FRAME>();
196 
197     if( aReason == MODEL_RELOAD || aReason == GAL_SWITCH )
198     {
199         m_placeOrigin->SetPosition( getModel<BOARD>()->GetDesignSettings().GetAuxOrigin() );
200         getView()->Remove( m_placeOrigin.get() );
201         getView()->Add( m_placeOrigin.get() );
202     }
203 }
204 
205 
Init()206 bool BOARD_EDITOR_CONTROL::Init()
207 {
208     auto activeToolCondition =
209             [this]( const SELECTION& aSel )
210             {
211                 return ( !m_frame->ToolStackIsEmpty() );
212             };
213 
214     auto inactiveStateCondition =
215             [this]( const SELECTION& aSel )
216             {
217                 return ( m_frame->ToolStackIsEmpty() && aSel.Size() == 0 );
218             };
219 
220     auto placeModuleCondition =
221             [this]( const SELECTION& aSel )
222             {
223                 return m_frame->IsCurrentTool( PCB_ACTIONS::placeFootprint ) && aSel.GetSize() == 0;
224             };
225 
226     auto& ctxMenu = m_menu.GetMenu();
227 
228     // "Cancel" goes at the top of the context menu when a tool is active
229     ctxMenu.AddItem( ACTIONS::cancelInteractive, activeToolCondition, 1 );
230     ctxMenu.AddSeparator( 1 );
231 
232     // "Get and Place Footprint" should be available for Place Footprint tool
233     ctxMenu.AddItem( PCB_ACTIONS::getAndPlace, placeModuleCondition, 1000 );
234     ctxMenu.AddSeparator( 1000 );
235 
236     // Finally, add the standard zoom & grid items
237     getEditFrame<PCB_BASE_FRAME>()->AddStandardSubMenus( m_menu );
238 
239     auto zoneMenu = std::make_shared<ZONE_CONTEXT_MENU>();
240     zoneMenu->SetTool( this );
241 
242     auto lockMenu = std::make_shared<LOCK_CONTEXT_MENU>();
243     lockMenu->SetTool( this );
244 
245     // Add the PCB control menus to relevant other tools
246 
247     PCB_SELECTION_TOOL* selTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
248 
249     if( selTool )
250     {
251         auto& toolMenu = selTool->GetToolMenu();
252         auto& menu = toolMenu.GetMenu();
253 
254         // Add "Get and Place Footprint" when Selection tool is in an inactive state
255         menu.AddItem( PCB_ACTIONS::getAndPlace, inactiveStateCondition );
256         menu.AddSeparator();
257 
258         toolMenu.AddSubMenu( zoneMenu );
259         toolMenu.AddSubMenu( lockMenu );
260 
261         menu.AddMenu( lockMenu.get(), SELECTION_CONDITIONS::NotEmpty, 100 );
262 
263         menu.AddMenu( zoneMenu.get(), SELECTION_CONDITIONS::OnlyType( PCB_ZONE_T ), 200 );
264     }
265 
266     DRAWING_TOOL* drawingTool = m_toolMgr->GetTool<DRAWING_TOOL>();
267 
268     if( drawingTool )
269     {
270         auto& toolMenu = drawingTool->GetToolMenu();
271         auto& menu = toolMenu.GetMenu();
272 
273         toolMenu.AddSubMenu( zoneMenu );
274 
275         // Functor to say if the PCB_EDIT_FRAME is in a given mode
276         // Capture the tool pointer and tool mode by value
277         auto toolActiveFunctor =
278                 [=]( DRAWING_TOOL::MODE aMode )
279                 {
280                     return [=]( const SELECTION& sel )
281                            {
282                                return drawingTool->GetDrawingMode() == aMode;
283                            };
284                 };
285 
286         menu.AddMenu( zoneMenu.get(), toolActiveFunctor( DRAWING_TOOL::MODE::ZONE ), 200 );
287     }
288 
289     return true;
290 }
291 
292 
New(const TOOL_EVENT & aEvent)293 int BOARD_EDITOR_CONTROL::New( const TOOL_EVENT& aEvent )
294 {
295     m_frame->Files_io_from_id( ID_NEW_BOARD );
296     return 0;
297 }
298 
299 
Open(const TOOL_EVENT & aEvent)300 int BOARD_EDITOR_CONTROL::Open( const TOOL_EVENT& aEvent )
301 {
302     m_frame->Files_io_from_id( ID_LOAD_FILE );
303     return 0;
304 }
305 
306 
Save(const TOOL_EVENT & aEvent)307 int BOARD_EDITOR_CONTROL::Save( const TOOL_EVENT& aEvent )
308 {
309     m_frame->Files_io_from_id( ID_SAVE_BOARD );
310     return 0;
311 }
312 
313 
SaveAs(const TOOL_EVENT & aEvent)314 int BOARD_EDITOR_CONTROL::SaveAs( const TOOL_EVENT& aEvent )
315 {
316     m_frame->Files_io_from_id( ID_SAVE_BOARD_AS );
317     return 0;
318 }
319 
320 
SaveCopyAs(const TOOL_EVENT & aEvent)321 int BOARD_EDITOR_CONTROL::SaveCopyAs( const TOOL_EVENT& aEvent )
322 {
323     m_frame->Files_io_from_id( ID_COPY_BOARD_AS );
324     return 0;
325 }
326 
327 
PageSettings(const TOOL_EVENT & aEvent)328 int BOARD_EDITOR_CONTROL::PageSettings( const TOOL_EVENT& aEvent )
329 {
330     PICKED_ITEMS_LIST   undoCmd;
331     DS_PROXY_UNDO_ITEM* undoItem = new DS_PROXY_UNDO_ITEM( m_frame );
332     ITEM_PICKER         wrapper( nullptr, undoItem, UNDO_REDO::PAGESETTINGS );
333 
334     undoCmd.PushItem( wrapper );
335     m_frame->SaveCopyInUndoList( undoCmd, UNDO_REDO::PAGESETTINGS );
336 
337     DIALOG_PAGES_SETTINGS dlg( m_frame, IU_PER_MILS, wxSize( MAX_PAGE_SIZE_PCBNEW_MILS,
338                                                              MAX_PAGE_SIZE_PCBNEW_MILS ) );
339     dlg.SetWksFileName( BASE_SCREEN::m_DrawingSheetFileName );
340 
341     if( dlg.ShowModal() == wxID_OK )
342         m_frame->OnModify();
343     else
344         m_frame->RollbackFromUndo();
345 
346     return 0;
347 }
348 
349 
Plot(const TOOL_EVENT & aEvent)350 int BOARD_EDITOR_CONTROL::Plot( const TOOL_EVENT& aEvent )
351 {
352     m_frame->ToPlotter( ID_GEN_PLOT );
353     return 0;
354 }
355 
356 
Find(const TOOL_EVENT & aEvent)357 int BOARD_EDITOR_CONTROL::Find( const TOOL_EVENT& aEvent )
358 {
359     m_frame->ShowFindDialog();
360     return 0;
361 }
362 
363 
FindNext(const TOOL_EVENT & aEvent)364 int BOARD_EDITOR_CONTROL::FindNext( const TOOL_EVENT& aEvent )
365 {
366     m_frame->FindNext();
367     return 0;
368 }
369 
370 
BoardSetup(const TOOL_EVENT & aEvent)371 int BOARD_EDITOR_CONTROL::BoardSetup( const TOOL_EVENT& aEvent )
372 {
373     getEditFrame<PCB_EDIT_FRAME>()->ShowBoardSetupDialog();
374     return 0;
375 }
376 
377 
ImportNetlist(const TOOL_EVENT & aEvent)378 int BOARD_EDITOR_CONTROL::ImportNetlist( const TOOL_EVENT& aEvent )
379 {
380     getEditFrame<PCB_EDIT_FRAME>()->InstallNetlistFrame();
381     return 0;
382 }
383 
384 
ImportSpecctraSession(const TOOL_EVENT & aEvent)385 int BOARD_EDITOR_CONTROL::ImportSpecctraSession( const TOOL_EVENT& aEvent )
386 {
387     wxString fullFileName = frame()->GetBoard()->GetFileName();
388     wxString path;
389     wxString name;
390     wxString ext;
391 
392     wxFileName::SplitPath( fullFileName, &path, &name, &ext );
393     name += wxT( "." ) + SpecctraSessionFileExtension;
394 
395     fullFileName = wxFileSelector( _( "Specctra Session File" ), path, name,
396                                    wxT( "." ) + SpecctraSessionFileExtension,
397                                    SpecctraSessionFileWildcard(), wxFD_OPEN | wxFD_CHANGE_DIR,
398                                    frame() );
399 
400     if( !fullFileName.IsEmpty() )
401         getEditFrame<PCB_EDIT_FRAME>()->ImportSpecctraSession( fullFileName );
402 
403     return 0;
404 }
405 
406 
ExportSpecctraDSN(const TOOL_EVENT & aEvent)407 int BOARD_EDITOR_CONTROL::ExportSpecctraDSN( const TOOL_EVENT& aEvent )
408 {
409     wxString    fullFileName = m_frame->GetLastPath( LAST_PATH_SPECCTRADSN );
410     wxFileName  fn;
411 
412     if( fullFileName.IsEmpty() )
413     {
414         fn = m_frame->GetBoard()->GetFileName();
415         fn.SetExt( SpecctraDsnFileExtension );
416     }
417     else
418     {
419         fn = fullFileName;
420     }
421 
422     fullFileName = wxFileSelector( _( "Specctra DSN File" ), fn.GetPath(), fn.GetFullName(),
423                                    SpecctraDsnFileExtension, SpecctraDsnFileWildcard(),
424                                    wxFD_SAVE | wxFD_OVERWRITE_PROMPT | wxFD_CHANGE_DIR, frame() );
425 
426     if( !fullFileName.IsEmpty() )
427     {
428         m_frame->SetLastPath( LAST_PATH_SPECCTRADSN, fullFileName );
429         getEditFrame<PCB_EDIT_FRAME>()->ExportSpecctraFile( fullFileName );
430     }
431 
432     return 0;
433 }
434 
435 
ExportNetlist(const TOOL_EVENT & aEvent)436 int BOARD_EDITOR_CONTROL::ExportNetlist( const TOOL_EVENT& aEvent )
437 {
438     wxCHECK( m_frame, 0 );
439 
440     wxFileName fn = m_frame->Prj().GetProjectFullName();
441 
442     // Use a different file extension for the board netlist so the schematic netlist file
443     // is accidentally overwritten.
444     fn.SetExt( "pcb_net" );
445 
446     wxFileDialog dlg( m_frame, _( "Export Board Netlist" ), fn.GetPath(), fn.GetFullName(),
447                       _( "KiCad board netlist files" ) + AddFileExtListToFilter( { "pcb_net" } ),
448                       wxFD_SAVE | wxFD_OVERWRITE_PROMPT );
449 
450     dlg.SetExtraControlCreator( &NETLIST_OPTIONS_HELPER::Create );
451 
452     if( dlg.ShowModal() == wxID_CANCEL )
453         return 0;
454 
455     fn = dlg.GetPath();
456 
457     if( !fn.IsDirWritable() )
458     {
459         wxString msg;
460 
461         msg.Printf( _( "Path `%s` is read only." ), fn.GetPath() );
462         wxMessageDialog( m_frame, msg, _( "I/O Error" ), wxOK | wxCENTER | wxICON_EXCLAMATION );
463         return 0;
464     }
465 
466     const NETLIST_OPTIONS_HELPER* noh =
467             dynamic_cast<const NETLIST_OPTIONS_HELPER*>( dlg.GetExtraControl() );
468     wxCHECK( noh, 0 );
469 
470     NETLIST netlist;
471 
472     for( const FOOTPRINT* footprint : board()->Footprints() )
473     {
474         COMPONENT* component = new COMPONENT( footprint->GetFPID(), footprint->GetReference(),
475                                               footprint->GetValue(), footprint->GetPath(),
476                                               { footprint->m_Uuid } );
477 
478         for( const PAD* pad : footprint->Pads() )
479         {
480             const wxString& netname = pad->GetShortNetname();
481 
482             if( !netname.IsEmpty() )
483             {
484                 component->AddNet( pad->GetNumber(), netname, pad->GetPinFunction(),
485                                    pad->GetPinType() );
486             }
487         }
488 
489         netlist.AddComponent( component );
490     }
491 
492     FILE_OUTPUTFORMATTER formatter( fn.GetFullPath() );
493 
494     netlist.Format( "pcb_netlist", &formatter, 0, noh->GetNetlistOptions() );
495 
496     return 0;
497 }
498 
499 
GenerateFabFiles(const TOOL_EVENT & aEvent)500 int BOARD_EDITOR_CONTROL::GenerateFabFiles( const TOOL_EVENT& aEvent )
501 {
502     wxCommandEvent dummy;
503 
504     if( aEvent.IsAction( &PCB_ACTIONS::generateGerbers ) )
505         m_frame->ToPlotter( ID_GEN_PLOT_GERBER );
506     else if( aEvent.IsAction( &PCB_ACTIONS::generateReportFile ) )
507         m_frame->GenFootprintsReport( dummy );
508     else if( aEvent.IsAction( &PCB_ACTIONS::generateD356File ) )
509         m_frame->GenD356File( dummy );
510     else if( aEvent.IsAction( &PCB_ACTIONS::generateBOM ) )
511         m_frame->RecreateBOMFileFromBoard( dummy );
512     else
513         wxFAIL_MSG( "GenerateFabFiles(): unexpected request" );
514 
515     return 0;
516 }
517 
518 
RepairBoard(const TOOL_EVENT & aEvent)519 int BOARD_EDITOR_CONTROL::RepairBoard( const TOOL_EVENT& aEvent )
520 {
521     int      errors = 0;
522     wxString details;
523     bool     quiet = aEvent.Parameter<bool>();
524 
525     // Repair duplicate IDs and missing nets.
526     std::set<KIID> ids;
527     int            duplicates = 0;
528 
529     auto processItem =
530             [&]( EDA_ITEM* aItem )
531             {
532                 if( ids.count( aItem->m_Uuid ) )
533                 {
534                     duplicates++;
535                     const_cast<KIID&>( aItem->m_Uuid ) = KIID();
536                 }
537 
538                 ids.insert( aItem->m_Uuid );
539 
540                 BOARD_CONNECTED_ITEM* cItem = dynamic_cast<BOARD_CONNECTED_ITEM*>( aItem );
541 
542                 if( cItem && cItem->GetNetCode() )
543                 {
544                     NETINFO_ITEM* netinfo = cItem->GetNet();
545 
546                     if( netinfo && !board()->FindNet( netinfo->GetNetname() ) )
547                     {
548                         board()->Add( netinfo );
549 
550                         details += wxString::Format( _( "Orphaned net %s re-parented.\n" ),
551                                                      netinfo->GetNetname() );
552                         errors++;
553                     }
554                 }
555             };
556 
557     // Footprint IDs are the most important, so give them the first crack at "claiming" a
558     // particular KIID.
559 
560     for( FOOTPRINT* footprint : board()->Footprints() )
561         processItem( footprint );
562 
563     // After that the principal use is for DRC marker pointers, which are most likely to pads
564     // or tracks.
565 
566     for( FOOTPRINT* footprint : board()->Footprints() )
567     {
568         for( PAD* pad : footprint->Pads() )
569             processItem( pad );
570     }
571 
572     for( PCB_TRACK* track : board()->Tracks() )
573         processItem( track );
574 
575     // From here out I don't think order matters much.
576 
577     for( FOOTPRINT* footprint : board()->Footprints() )
578     {
579         processItem( &footprint->Reference() );
580         processItem( &footprint->Value() );
581 
582         for( BOARD_ITEM* item : footprint->GraphicalItems() )
583             processItem( item );
584 
585         for( ZONE* zone : footprint->Zones() )
586             processItem( zone );
587 
588         for( PCB_GROUP* group : footprint->Groups() )
589             processItem( group );
590     }
591 
592     for( BOARD_ITEM* drawing : board()->Drawings() )
593         processItem( drawing );
594 
595     for( ZONE* zone : board()->Zones() )
596         processItem( zone );
597 
598     for( PCB_MARKER* marker : board()->Markers() )
599         processItem( marker );
600 
601     for( PCB_GROUP* group : board()->Groups() )
602         processItem( group );
603 
604     if( duplicates )
605     {
606         errors += duplicates;
607         details += wxString::Format( _( "%d duplicate IDs replaced.\n" ), duplicates );
608     }
609 
610     /*******************************
611      * Your test here
612      */
613 
614     /*******************************
615      * Inform the user
616      */
617 
618     if( errors )
619     {
620         m_frame->OnModify();
621 
622         wxString msg = wxString::Format( _( "%d potential problems repaired." ), errors );
623 
624         if( !quiet )
625             DisplayInfoMessage( m_frame, msg, details );
626     }
627     else if( !quiet )
628     {
629         DisplayInfoMessage( m_frame, _( "No board problems found." ) );
630     }
631 
632     return 0;
633 }
634 
635 
UpdatePCBFromSchematic(const TOOL_EVENT & aEvent)636 int BOARD_EDITOR_CONTROL::UpdatePCBFromSchematic( const TOOL_EVENT& aEvent )
637 {
638     NETLIST netlist;
639 
640     if( m_frame->FetchNetlistFromSchematic( netlist, _( "Updating PCB requires a fully annotated "
641                                                         "schematic." ) ) )
642     {
643         DIALOG_UPDATE_PCB updateDialog( m_frame, &netlist );
644         updateDialog.ShowModal();
645     }
646 
647     return 0;
648 }
649 
UpdateSchematicFromPCB(const TOOL_EVENT & aEvent)650 int BOARD_EDITOR_CONTROL::UpdateSchematicFromPCB( const TOOL_EVENT& aEvent )
651 {
652     if( Kiface().IsSingle() )
653     {
654         DisplayErrorMessage(
655                 m_frame, _( "Cannot update schematic because Pcbnew is opened in stand-alone "
656                             "mode. In order to create or update PCBs from schematics, you "
657                             "must launch the KiCad project manager and create a project." ) );
658         return 0;
659     }
660 
661     m_frame->RunEeschema();
662     KIWAY_PLAYER* frame = m_frame->Kiway().Player( FRAME_SCH, false );
663 
664     if( frame )
665     {
666         std::string payload;
667         m_frame->Kiway().ExpressMail( FRAME_SCH, MAIL_SCH_UPDATE, payload, m_frame );
668     }
669     return 0;
670 }
671 
672 
ShowEeschema(const TOOL_EVENT & aEvent)673 int BOARD_EDITOR_CONTROL::ShowEeschema( const TOOL_EVENT& aEvent )
674 {
675     m_frame->RunEeschema();
676     return 0;
677 }
678 
679 
ToggleLayersManager(const TOOL_EVENT & aEvent)680 int BOARD_EDITOR_CONTROL::ToggleLayersManager( const TOOL_EVENT& aEvent )
681 {
682     getEditFrame<PCB_EDIT_FRAME>()->ToggleLayersManager();
683     return 0;
684 }
685 
686 
TogglePythonConsole(const TOOL_EVENT & aEvent)687 int BOARD_EDITOR_CONTROL::TogglePythonConsole( const TOOL_EVENT& aEvent )
688 {
689     m_frame->ScriptingConsoleEnableDisable();
690     return 0;
691 }
692 
693 
694 // Track & via size control
TrackWidthInc(const TOOL_EVENT & aEvent)695 int BOARD_EDITOR_CONTROL::TrackWidthInc( const TOOL_EVENT& aEvent )
696 {
697     BOARD_DESIGN_SETTINGS& designSettings = getModel<BOARD>()->GetDesignSettings();
698     constexpr KICAD_T      types[] = { PCB_TRACE_T, PCB_VIA_T, EOT };
699     PCB_SELECTION&         selection = m_toolMgr->GetTool<PCB_SELECTION_TOOL>()->GetSelection();
700 
701     if( m_frame->ToolStackIsEmpty() && SELECTION_CONDITIONS::OnlyTypes( types )( selection ) )
702     {
703         BOARD_COMMIT commit( this );
704 
705         for( EDA_ITEM* item : selection )
706         {
707             if( item->Type() == PCB_TRACE_T )
708             {
709                 PCB_TRACK* track = static_cast<PCB_TRACK*>( item );
710 
711                 // Note: skip first entry which is the current netclass value
712                 for( int i = 1; i < (int) designSettings.m_TrackWidthList.size(); ++i )
713                 {
714                     int candidate = designSettings.m_TrackWidthList[ i ];
715 
716                     if( candidate > track->GetWidth() )
717                     {
718                         commit.Modify( track );
719                         track->SetWidth( candidate );
720                         break;
721                     }
722                 }
723             }
724         }
725 
726         commit.Push( "Increase Track Width" );
727         return 0;
728     }
729 
730     ROUTER_TOOL* routerTool = m_toolMgr->GetTool<ROUTER_TOOL>();
731 
732     if( routerTool && routerTool->IsToolActive()
733             && routerTool->Router()->Mode() == PNS::PNS_MODE_ROUTE_DIFF_PAIR )
734     {
735         int widthIndex = designSettings.GetDiffPairIndex() + 1;
736 
737         // If we go past the last track width entry in the list, start over at the beginning
738         if( widthIndex >= (int) designSettings.m_DiffPairDimensionsList.size() )
739             widthIndex = 0;
740 
741         designSettings.SetDiffPairIndex( widthIndex );
742         designSettings.UseCustomDiffPairDimensions( false );
743 
744         m_toolMgr->RunAction( PCB_ACTIONS::trackViaSizeChanged, true );
745     }
746     else
747     {
748         int widthIndex = designSettings.GetTrackWidthIndex();
749 
750         if( routerTool && routerTool->IsToolActive()
751             && routerTool->Router()->GetState() == PNS::ROUTER::RouterState::ROUTE_TRACK
752             && designSettings.m_UseConnectedTrackWidth && !designSettings.m_TempOverrideTrackWidth )
753         {
754             designSettings.m_TempOverrideTrackWidth = true;
755         }
756         else
757         {
758             widthIndex++;
759         }
760 
761         // If we go past the last track width entry in the list, start over at the beginning
762         if( widthIndex >= (int) designSettings.m_TrackWidthList.size() )
763             widthIndex = 0;
764 
765         designSettings.SetTrackWidthIndex( widthIndex );
766         designSettings.UseCustomTrackViaSize( false );
767 
768         m_toolMgr->RunAction( PCB_ACTIONS::trackViaSizeChanged, true );
769     }
770 
771     return 0;
772 }
773 
774 
TrackWidthDec(const TOOL_EVENT & aEvent)775 int BOARD_EDITOR_CONTROL::TrackWidthDec( const TOOL_EVENT& aEvent )
776 {
777     BOARD_DESIGN_SETTINGS& designSettings = getModel<BOARD>()->GetDesignSettings();
778     constexpr KICAD_T      types[] = { PCB_TRACE_T, PCB_VIA_T, EOT };
779     PCB_SELECTION&         selection = m_toolMgr->GetTool<PCB_SELECTION_TOOL>()->GetSelection();
780 
781     if( m_frame->ToolStackIsEmpty() && SELECTION_CONDITIONS::OnlyTypes( types )( selection ) )
782     {
783         BOARD_COMMIT commit( this );
784 
785         for( EDA_ITEM* item : selection )
786         {
787             if( item->Type() == PCB_TRACE_T )
788             {
789                 PCB_TRACK* track = static_cast<PCB_TRACK*>( item );
790 
791                 // Note: skip first entry which is the current netclass value
792                 for( int i = designSettings.m_TrackWidthList.size() - 1; i >= 1; --i )
793                 {
794                     int candidate = designSettings.m_TrackWidthList[ i ];
795 
796                     if( candidate < track->GetWidth() )
797                     {
798                         commit.Modify( track );
799                         track->SetWidth( candidate );
800                         break;
801                     }
802                 }
803             }
804         }
805 
806         commit.Push( "Decrease Track Width" );
807         return 0;
808     }
809 
810     ROUTER_TOOL* routerTool = m_toolMgr->GetTool<ROUTER_TOOL>();
811 
812     if( routerTool && routerTool->IsToolActive()
813             && routerTool->Router()->Mode() == PNS::PNS_MODE_ROUTE_DIFF_PAIR )
814     {
815         int widthIndex = designSettings.GetDiffPairIndex() - 1;
816 
817         // If we get to the lowest entry start over at the highest
818         if( widthIndex < 0 )
819             widthIndex = designSettings.m_DiffPairDimensionsList.size() - 1;
820 
821         designSettings.SetDiffPairIndex( widthIndex );
822         designSettings.UseCustomDiffPairDimensions( false );
823 
824         m_toolMgr->RunAction( PCB_ACTIONS::trackViaSizeChanged, true );
825     }
826     else
827     {
828         int widthIndex = designSettings.GetTrackWidthIndex();
829 
830         if( routerTool && routerTool->IsToolActive()
831             && routerTool->Router()->GetState() == PNS::ROUTER::RouterState::ROUTE_TRACK
832             && designSettings.m_UseConnectedTrackWidth && !designSettings.m_TempOverrideTrackWidth )
833         {
834             designSettings.m_TempOverrideTrackWidth = true;
835         }
836         else
837         {
838             widthIndex--;
839         }
840 
841         // If we get to the lowest entry start over at the highest
842         if( widthIndex < 0 )
843             widthIndex = designSettings.m_TrackWidthList.size() - 1;
844 
845         designSettings.SetTrackWidthIndex( widthIndex );
846         designSettings.UseCustomTrackViaSize( false );
847 
848         m_toolMgr->RunAction( PCB_ACTIONS::trackViaSizeChanged, true );
849     }
850 
851     return 0;
852 }
853 
854 
ViaSizeInc(const TOOL_EVENT & aEvent)855 int BOARD_EDITOR_CONTROL::ViaSizeInc( const TOOL_EVENT& aEvent )
856 {
857     BOARD_DESIGN_SETTINGS& designSettings = getModel<BOARD>()->GetDesignSettings();
858     constexpr KICAD_T      types[] = { PCB_TRACE_T, PCB_VIA_T, EOT };
859     PCB_SELECTION&         selection = m_toolMgr->GetTool<PCB_SELECTION_TOOL>()->GetSelection();
860 
861     if( m_frame->ToolStackIsEmpty() && SELECTION_CONDITIONS::OnlyTypes( types )( selection ) )
862     {
863         BOARD_COMMIT commit( this );
864 
865         for( EDA_ITEM* item : selection )
866         {
867             if( item->Type() == PCB_VIA_T )
868             {
869                 PCB_VIA* via = static_cast<PCB_VIA*>( item );
870 
871                 for( VIA_DIMENSION candidate : designSettings.m_ViasDimensionsList )
872                 {
873                     if( candidate.m_Diameter > via->GetWidth() )
874                     {
875                         commit.Modify( via );
876                         via->SetWidth( candidate.m_Diameter );
877                         via->SetDrill( candidate.m_Drill );
878                         break;
879                     }
880                 }
881             }
882         }
883 
884         commit.Push( "Increase Via Size" );
885     }
886     else
887     {
888         int sizeIndex = designSettings.GetViaSizeIndex() + 1;
889 
890         // If we go past the last via entry in the list, start over at the beginning
891         if( sizeIndex >= (int) designSettings.m_ViasDimensionsList.size() )
892             sizeIndex = 0;
893 
894         designSettings.SetViaSizeIndex( sizeIndex );
895         designSettings.UseCustomTrackViaSize( false );
896 
897         m_toolMgr->RunAction( PCB_ACTIONS::trackViaSizeChanged, true );
898     }
899 
900     return 0;
901 }
902 
903 
ViaSizeDec(const TOOL_EVENT & aEvent)904 int BOARD_EDITOR_CONTROL::ViaSizeDec( const TOOL_EVENT& aEvent )
905 {
906     BOARD_DESIGN_SETTINGS& designSettings = getModel<BOARD>()->GetDesignSettings();
907     constexpr KICAD_T      types[] = { PCB_TRACE_T, PCB_VIA_T, EOT };
908     PCB_SELECTION&         selection = m_toolMgr->GetTool<PCB_SELECTION_TOOL>()->GetSelection();
909 
910     if( m_frame->ToolStackIsEmpty() && SELECTION_CONDITIONS::OnlyTypes( types )( selection ) )
911     {
912         BOARD_COMMIT commit( this );
913 
914         for( EDA_ITEM* item : selection )
915         {
916             if( item->Type() == PCB_VIA_T )
917             {
918                 PCB_VIA* via = static_cast<PCB_VIA*>( item );
919 
920                 for( int i = designSettings.m_ViasDimensionsList.size() - 1; i >= 0; --i )
921                 {
922                     VIA_DIMENSION candidate = designSettings.m_ViasDimensionsList[ i ];
923 
924                     if( candidate.m_Diameter < via->GetWidth() )
925                     {
926                         commit.Modify( via );
927                         via->SetWidth( candidate.m_Diameter );
928                         via->SetDrill( candidate.m_Drill );
929                         break;
930                     }
931                 }
932             }
933         }
934 
935         commit.Push( "Decrease Via Size" );
936     }
937     else
938     {
939         int sizeIndex = 0; // Assume we only have a single via size entry
940 
941         // If there are more, cycle through them backwards
942         if( designSettings.m_ViasDimensionsList.size() > 0 )
943         {
944             sizeIndex = designSettings.GetViaSizeIndex() - 1;
945 
946             // If we get to the lowest entry start over at the highest
947             if( sizeIndex < 0 )
948                 sizeIndex = designSettings.m_ViasDimensionsList.size() - 1;
949         }
950 
951         designSettings.SetViaSizeIndex( sizeIndex );
952         designSettings.UseCustomTrackViaSize( false );
953 
954         m_toolMgr->RunAction( PCB_ACTIONS::trackViaSizeChanged, true );
955     }
956 
957     return 0;
958 }
959 
960 
PlaceFootprint(const TOOL_EVENT & aEvent)961 int BOARD_EDITOR_CONTROL::PlaceFootprint( const TOOL_EVENT& aEvent )
962 {
963     if( m_inPlaceFootprint )
964         return 0;
965 
966     REENTRANCY_GUARD guard( &m_inPlaceFootprint );
967 
968     FOOTPRINT*            fp = aEvent.Parameter<FOOTPRINT*>();
969     KIGFX::VIEW_CONTROLS* controls = getViewControls();
970     BOARD_COMMIT          commit( m_frame );
971     BOARD*                board = getModel<BOARD>();
972 
973     m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
974 
975     std::string tool = aEvent.GetCommandStr().get();
976     m_frame->PushTool( tool );
977 
978     auto setCursor =
979             [&]()
980             {
981                 m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::PENCIL );
982             };
983 
984     Activate();
985     // Must be done after Activate() so that it gets set into the correct context
986     controls->ShowCursor( true );
987     // Set initial cursor
988     setCursor();
989 
990     VECTOR2I cursorPos = controls->GetCursorPosition();
991     bool     reselect = false;
992     bool     fromOtherCommand = fp != nullptr;
993     bool     resetCursor = aEvent.HasPosition(); // Detect if activated from a hotkey.
994 
995     // Prime the pump
996     if( fp )
997     {
998         m_placingFootprint = true;
999         fp->SetPosition( wxPoint( cursorPos.x, cursorPos.y ) );
1000         m_toolMgr->RunAction( PCB_ACTIONS::selectItem, true, fp );
1001         m_toolMgr->RunAction( ACTIONS::refreshPreview );
1002     }
1003     else if( !aEvent.IsReactivate() )
1004     {
1005         m_toolMgr->RunAction( PCB_ACTIONS::cursorClick );
1006     }
1007 
1008     // Main loop: keep receiving events
1009     while( TOOL_EVENT* evt = Wait() )
1010     {
1011         setCursor();
1012         cursorPos = controls->GetCursorPosition( !evt->DisableGridSnapping() );
1013 
1014         if( reselect && fp )
1015             m_toolMgr->RunAction( PCB_ACTIONS::selectItem, true, fp );
1016 
1017         auto cleanup =
1018                 [&] ()
1019                 {
1020                     m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
1021                     commit.Revert();
1022 
1023                     if( fromOtherCommand )
1024                     {
1025                         PICKED_ITEMS_LIST* undo = m_frame->PopCommandFromUndoList();
1026 
1027                         if( undo )
1028                         {
1029                             m_frame->PutDataInPreviousState( undo );
1030                             undo->ClearListAndDeleteItems();
1031                             delete undo;
1032                         }
1033                     }
1034 
1035                     fp = nullptr;
1036                     m_placingFootprint = false;
1037                 };
1038 
1039         if( evt->IsCancelInteractive() )
1040         {
1041             if( fp )
1042             {
1043                 cleanup();
1044             }
1045             else
1046             {
1047                 m_frame->PopTool( tool );
1048                 break;
1049             }
1050         }
1051         else if( evt->IsActivate() )
1052         {
1053             if( fp )
1054                 cleanup();
1055 
1056             if( evt->IsMoveTool() )
1057             {
1058                 // leave ourselves on the stack so we come back after the move
1059                 break;
1060             }
1061             else
1062             {
1063                 frame()->PopTool( tool );
1064                 break;
1065             }
1066         }
1067         else if( evt->IsClick( BUT_LEFT ) )
1068         {
1069             if( !fp )
1070             {
1071                 // Pick the footprint to be placed
1072                 fp = m_frame->SelectFootprintFromLibTree();
1073 
1074                 if( fp == nullptr )
1075                     continue;
1076 
1077                 m_placingFootprint = true;
1078 
1079                 fp->SetLink( niluuid );
1080 
1081                 fp->SetFlags(IS_NEW ); // whatever
1082 
1083                 // Set parent so that clearance can be loaded
1084                 fp->SetParent( board );
1085 
1086                 for( PAD* pad : fp->Pads() )
1087                 {
1088                     pad->SetLocalRatsnestVisible( m_frame->GetDisplayOptions().m_ShowGlobalRatsnest );
1089 
1090                     // Pads in the library all have orphaned nets.  Replace with Default.
1091                     pad->SetNetCode( 0 );
1092                 }
1093 
1094                 // Put it on FRONT layer,
1095                 // (Can be stored flipped if the lib is an archive built from a board)
1096                 if( fp->IsFlipped() )
1097                     fp->Flip( fp->GetPosition(), m_frame->Settings().m_FlipLeftRight );
1098 
1099                 fp->SetOrientation( 0 );
1100                 fp->SetPosition( wxPoint( cursorPos.x, cursorPos.y ) );
1101 
1102                 commit.Add( fp );
1103                 m_toolMgr->RunAction( PCB_ACTIONS::selectItem, true, fp );
1104 
1105                 // Reset cursor to the position before the dialog opened if activated from hotkey
1106                 if( resetCursor )
1107                     controls->SetCursorPosition( cursorPos, false );
1108 
1109                 // Other events must be from hotkeys or mouse clicks, so always reset cursor
1110                 resetCursor = true;
1111 
1112                 m_toolMgr->RunAction( ACTIONS::refreshPreview );
1113             }
1114             else
1115             {
1116                 m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
1117                 commit.Push( _( "Place a footprint" ) );
1118                 fp = nullptr;  // to indicate that there is no footprint that we currently modify
1119                 m_placingFootprint = false;
1120             }
1121         }
1122         else if( evt->IsClick( BUT_RIGHT ) )
1123         {
1124             m_menu.ShowContextMenu(  selection()  );
1125         }
1126         else if( fp && ( evt->IsMotion() || evt->IsAction( &ACTIONS::refreshPreview ) ) )
1127         {
1128             fp->SetPosition( wxPoint( cursorPos.x, cursorPos.y ) );
1129             selection().SetReferencePoint( cursorPos );
1130             getView()->Update( &selection() );
1131             getView()->Update( fp );
1132         }
1133         else if( fp && evt->IsAction( &PCB_ACTIONS::properties ) )
1134         {
1135             // Calling 'Properties' action clears the selection, so we need to restore it
1136             reselect = true;
1137         }
1138         else
1139         {
1140             evt->SetPassEvent();
1141         }
1142 
1143         // Enable autopanning and cursor capture only when there is a footprint to be placed
1144         controls->SetAutoPan( fp != nullptr );
1145         controls->CaptureCursor( fp != nullptr );
1146     }
1147 
1148     controls->SetAutoPan( false );
1149     controls->CaptureCursor( false );
1150     m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::ARROW );
1151 
1152     return 0;
1153 }
1154 
1155 
ToggleLockSelected(const TOOL_EVENT & aEvent)1156 int BOARD_EDITOR_CONTROL::ToggleLockSelected( const TOOL_EVENT& aEvent )
1157 {
1158     return modifyLockSelected( TOGGLE );
1159 }
1160 
1161 
LockSelected(const TOOL_EVENT & aEvent)1162 int BOARD_EDITOR_CONTROL::LockSelected( const TOOL_EVENT& aEvent )
1163 {
1164     return modifyLockSelected( ON );
1165 }
1166 
1167 
UnlockSelected(const TOOL_EVENT & aEvent)1168 int BOARD_EDITOR_CONTROL::UnlockSelected( const TOOL_EVENT& aEvent )
1169 {
1170     return modifyLockSelected( OFF );
1171 }
1172 
1173 
modifyLockSelected(MODIFY_MODE aMode)1174 int BOARD_EDITOR_CONTROL::modifyLockSelected( MODIFY_MODE aMode )
1175 {
1176     PCB_SELECTION_TOOL*  selTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
1177     const PCB_SELECTION& selection = selTool->GetSelection();
1178     BOARD_COMMIT         commit( m_frame );
1179 
1180     if( selection.Empty() )
1181         m_toolMgr->RunAction( PCB_ACTIONS::selectionCursor, true );
1182 
1183     // Resolve TOGGLE mode
1184     if( aMode == TOGGLE )
1185     {
1186         aMode = ON;
1187 
1188         for( EDA_ITEM* item : selection )
1189         {
1190             BOARD_ITEM* board_item = static_cast<BOARD_ITEM*>( item );
1191 
1192             if( board_item->IsLocked() )
1193             {
1194                 aMode = OFF;
1195                 break;
1196             }
1197         }
1198     }
1199 
1200     bool modified = false;
1201 
1202     for( EDA_ITEM* item : selection )
1203     {
1204         BOARD_ITEM* board_item = static_cast<BOARD_ITEM*>( item );
1205 
1206         commit.Modify( board_item );
1207 
1208         if( aMode == ON )
1209         {
1210             modified |= !board_item->IsLocked();
1211             board_item->SetLocked( true );
1212         }
1213         else
1214         {
1215             modified |= board_item->IsLocked();
1216             board_item->SetLocked( false );
1217         }
1218     }
1219 
1220     if( modified )
1221     {
1222         commit.Push( aMode == ON ? _( "Lock" ) : _( "Unlock" ) );
1223 
1224         m_toolMgr->PostEvent( EVENTS::SelectedEvent );
1225         m_frame->UpdateMsgPanel();
1226         m_frame->OnModify();
1227     }
1228 
1229     return 0;
1230 }
1231 
1232 
PlaceTarget(const TOOL_EVENT & aEvent)1233 int BOARD_EDITOR_CONTROL::PlaceTarget( const TOOL_EVENT& aEvent )
1234 {
1235     if( m_inPlaceTarget )
1236         return 0;
1237 
1238     REENTRANCY_GUARD guard( &m_inPlaceTarget );
1239 
1240     KIGFX::VIEW* view = getView();
1241     KIGFX::VIEW_CONTROLS* controls = getViewControls();
1242     BOARD* board = getModel<BOARD>();
1243     PCB_TARGET* target = new PCB_TARGET( board );
1244 
1245     // Init the new item attributes
1246     target->SetLayer( Edge_Cuts );
1247     target->SetWidth( board->GetDesignSettings().GetLineThickness( Edge_Cuts ) );
1248     target->SetSize( Millimeter2iu( 5 ) );
1249     VECTOR2I cursorPos = controls->GetCursorPosition();
1250     target->SetPosition( wxPoint( cursorPos.x, cursorPos.y ) );
1251 
1252     // Add a VIEW_GROUP that serves as a preview for the new item
1253     KIGFX::VIEW_GROUP preview( view );
1254     preview.Add( target );
1255     view->Add( &preview );
1256 
1257     m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
1258 
1259     std::string tool = aEvent.GetCommandStr().get();
1260     m_frame->PushTool( tool );
1261     Activate();
1262 
1263     auto setCursor =
1264             [&]()
1265             {
1266                 m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::ARROW );
1267             };
1268 
1269     // Set initial cursor
1270     setCursor();
1271 
1272     // Main loop: keep receiving events
1273     while( TOOL_EVENT* evt = Wait() )
1274     {
1275         setCursor();
1276         cursorPos = controls->GetCursorPosition( !evt->DisableGridSnapping() );
1277 
1278         if( evt->IsCancelInteractive() )
1279         {
1280             frame()->PopTool( tool );
1281             break;
1282         }
1283         else if( evt->IsActivate() )
1284         {
1285             if( evt->IsMoveTool() )
1286             {
1287                 // leave ourselves on the stack so we come back after the move
1288                 break;
1289             }
1290             else
1291             {
1292                 frame()->PopTool( tool );
1293                 break;
1294             }
1295         }
1296         else if( evt->IsAction( &PCB_ACTIONS::incWidth ) )
1297         {
1298             target->SetWidth( target->GetWidth() + WIDTH_STEP );
1299             view->Update( &preview );
1300         }
1301         else if( evt->IsAction( &PCB_ACTIONS::decWidth ) )
1302         {
1303             int width = target->GetWidth();
1304 
1305             if( width > WIDTH_STEP )
1306             {
1307                 target->SetWidth( width - WIDTH_STEP );
1308                 view->Update( &preview );
1309             }
1310         }
1311         else if( evt->IsClick( BUT_LEFT ) )
1312         {
1313             assert( target->GetSize() > 0 );
1314             assert( target->GetWidth() > 0 );
1315 
1316             BOARD_COMMIT commit( m_frame );
1317             commit.Add( target );
1318             commit.Push( "Place a layer alignment target" );
1319 
1320             preview.Remove( target );
1321 
1322             // Create next PCB_TARGET
1323             target = new PCB_TARGET( *target );
1324             preview.Add( target );
1325         }
1326         else if( evt->IsClick( BUT_RIGHT ) )
1327         {
1328             m_menu.ShowContextMenu( selection() );
1329         }
1330         else if( evt->IsMotion() )
1331         {
1332             target->SetPosition( wxPoint( cursorPos.x, cursorPos.y ) );
1333             view->Update( &preview );
1334         }
1335         else
1336         {
1337             evt->SetPassEvent();
1338         }
1339     }
1340 
1341     preview.Clear();
1342     delete target;
1343     view->Remove( &preview );
1344 
1345     m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::ARROW );
1346 
1347     return 0;
1348 }
1349 
1350 
mergeZones(BOARD_COMMIT & aCommit,std::vector<ZONE * > & aOriginZones,std::vector<ZONE * > & aMergedZones)1351 static bool mergeZones( BOARD_COMMIT& aCommit, std::vector<ZONE*>& aOriginZones,
1352                         std::vector<ZONE*>& aMergedZones )
1353 {
1354     aCommit.Modify( aOriginZones[0] );
1355 
1356     for( unsigned int i = 1; i < aOriginZones.size(); i++ )
1357     {
1358         aOriginZones[0]->Outline()->BooleanAdd( *aOriginZones[i]->Outline(),
1359                                                 SHAPE_POLY_SET::PM_FAST );
1360     }
1361 
1362     aOriginZones[0]->Outline()->Simplify( SHAPE_POLY_SET::PM_FAST );
1363 
1364     // We should have one polygon with hole
1365     // We can have 2 polygons with hole, if the 2 initial polygons have only one common corner
1366     // and therefore cannot be merged (they are detected as intersecting)
1367     // but we should never have more than 2 polys
1368     if( aOriginZones[0]->Outline()->OutlineCount() > 1 )
1369     {
1370         wxLogMessage( "BOARD::mergeZones error: more than 2 polys after merging" );
1371         return false;
1372     }
1373 
1374     for( unsigned int i = 1; i < aOriginZones.size(); i++ )
1375         aCommit.Remove( aOriginZones[i] );
1376 
1377     aMergedZones.push_back( aOriginZones[0] );
1378 
1379     aOriginZones[0]->SetLocalFlags( 1 );
1380     aOriginZones[0]->HatchBorder();
1381     aOriginZones[0]->CacheTriangulation();
1382 
1383     return true;
1384 }
1385 
1386 
ZoneMerge(const TOOL_EVENT & aEvent)1387 int BOARD_EDITOR_CONTROL::ZoneMerge( const TOOL_EVENT& aEvent )
1388 {
1389     const PCB_SELECTION& selection = m_toolMgr->GetTool<PCB_SELECTION_TOOL>()->GetSelection();
1390     BOARD*               board = getModel<BOARD>();
1391     BOARD_COMMIT         commit( m_frame );
1392 
1393     if( selection.Size() < 2 )
1394         return 0;
1395 
1396     int netcode = -1;
1397 
1398     ZONE* firstZone = nullptr;
1399     std::vector<ZONE*> toMerge, merged;
1400 
1401     for( EDA_ITEM* item : selection )
1402     {
1403         ZONE* curr_area = dynamic_cast<ZONE*>( item );
1404 
1405         if( !curr_area )
1406             continue;
1407 
1408         if( !firstZone )
1409             firstZone = curr_area;
1410 
1411         netcode = curr_area->GetNetCode();
1412 
1413         if( firstZone->GetNetCode() != netcode )
1414             continue;
1415 
1416         if( curr_area->GetPriority() != firstZone->GetPriority() )
1417             continue;
1418 
1419         if( curr_area->GetIsRuleArea() != firstZone->GetIsRuleArea() )
1420             continue;
1421 
1422         if( curr_area->GetLayer() != firstZone->GetLayer() )
1423             continue;
1424 
1425         if( !board->TestZoneIntersection( curr_area, firstZone ) )
1426             continue;
1427 
1428         toMerge.push_back( curr_area );
1429     }
1430 
1431     m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
1432 
1433     if( mergeZones( commit, toMerge, merged ) )
1434     {
1435         commit.Push( "Merge zones" );
1436 
1437         for( EDA_ITEM* item : merged )
1438             m_toolMgr->RunAction( PCB_ACTIONS::selectItem, true, item );
1439     }
1440 
1441     return 0;
1442 }
1443 
1444 
ZoneDuplicate(const TOOL_EVENT & aEvent)1445 int BOARD_EDITOR_CONTROL::ZoneDuplicate( const TOOL_EVENT& aEvent )
1446 {
1447     PCB_SELECTION_TOOL*  selTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
1448     const PCB_SELECTION& selection = selTool->GetSelection();
1449 
1450     // because this pops up the zone editor, it would be confusing to handle multiple zones,
1451     // so just handle single selections containing exactly one zone
1452     if( selection.Size() != 1 )
1453         return 0;
1454 
1455     ZONE* oldZone = dyn_cast<ZONE*>( selection[0] );
1456 
1457     if( !oldZone )
1458         return 0;
1459 
1460     ZONE_SETTINGS zoneSettings;
1461     zoneSettings << *oldZone;
1462     int dialogResult;
1463 
1464     if( oldZone->GetIsRuleArea() )
1465         dialogResult = InvokeRuleAreaEditor( m_frame, &zoneSettings );
1466     else if( oldZone->IsOnCopperLayer() )
1467         dialogResult = InvokeCopperZonesEditor( m_frame, &zoneSettings );
1468     else
1469         dialogResult = InvokeNonCopperZonesEditor( m_frame, &zoneSettings );
1470 
1471     if( dialogResult != wxID_OK )
1472         return 0;
1473 
1474     // duplicate the zone
1475     BOARD_COMMIT commit( m_frame );
1476 
1477     std::unique_ptr<ZONE> newZone = std::make_unique<ZONE>( *oldZone );
1478     newZone->ClearSelected();
1479     newZone->UnFill();
1480     zoneSettings.ExportSetting( *newZone );
1481 
1482     // If the new zone is on the same layer(s) as the initial zone,
1483     // offset it a bit so it can more easily be picked.
1484     if( oldZone->GetIsRuleArea() && ( oldZone->GetLayerSet() == zoneSettings.m_Layers ) )
1485         newZone->Move( wxPoint( IU_PER_MM, IU_PER_MM ) );
1486     else if( !oldZone->GetIsRuleArea() && zoneSettings.m_Layers.test( oldZone->GetLayer() ) )
1487         newZone->Move( wxPoint( IU_PER_MM, IU_PER_MM ) );
1488 
1489     commit.Add( newZone.release() );
1490     commit.Push( _( "Duplicate zone" ) );
1491 
1492     return 0;
1493 }
1494 
1495 
EditFpInFpEditor(const TOOL_EVENT & aEvent)1496 int BOARD_EDITOR_CONTROL::EditFpInFpEditor( const TOOL_EVENT& aEvent )
1497 {
1498     PCB_SELECTION_TOOL*  selTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
1499     const PCB_SELECTION& selection = selTool->RequestSelection( EDIT_TOOL::FootprintFilter );
1500 
1501     if( selection.Empty() )
1502         return 0;
1503 
1504     FOOTPRINT* fp = selection.FirstOfKind<FOOTPRINT>();
1505 
1506     if( !fp )
1507         return 0;
1508 
1509     PCB_BASE_EDIT_FRAME* editFrame = getEditFrame<PCB_BASE_EDIT_FRAME>();
1510 
1511     auto editor = (FOOTPRINT_EDIT_FRAME*) editFrame->Kiway().Player( FRAME_FOOTPRINT_EDITOR, true );
1512 
1513     if( aEvent.IsAction( &PCB_ACTIONS::editFpInFpEditor ) )
1514         editor->LoadFootprintFromBoard( fp );
1515     else if( aEvent.IsAction( &PCB_ACTIONS::editLibFpInFpEditor ) )
1516         editor->LoadFootprintFromLibrary( fp->GetFPID() );
1517 
1518     editor->Show( true );
1519     editor->Raise();        // Iconize( false );
1520 
1521     if( selection.IsHover() )
1522         m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
1523 
1524     return 0;
1525 }
1526 
1527 
DoSetDrillOrigin(KIGFX::VIEW * aView,PCB_BASE_FRAME * aFrame,EDA_ITEM * originViewItem,const VECTOR2D & aPosition)1528 void BOARD_EDITOR_CONTROL::DoSetDrillOrigin( KIGFX::VIEW* aView, PCB_BASE_FRAME* aFrame,
1529                                              EDA_ITEM* originViewItem, const VECTOR2D& aPosition )
1530 {
1531     aFrame->GetDesignSettings().SetAuxOrigin( wxPoint( aPosition ) );
1532     originViewItem->SetPosition( (wxPoint) aPosition );
1533     aView->MarkDirty();
1534     aFrame->OnModify();
1535 }
1536 
1537 
DrillOrigin(const TOOL_EVENT & aEvent)1538 int BOARD_EDITOR_CONTROL::DrillOrigin( const TOOL_EVENT& aEvent )
1539 {
1540     std::string      tool = aEvent.GetCommandStr().get();
1541     PCB_PICKER_TOOL* picker = m_toolMgr->GetTool<PCB_PICKER_TOOL>();
1542 
1543     // Deactivate other tools; particularly important if another PICKER is currently running
1544     Activate();
1545 
1546     picker->SetClickHandler(
1547         [this] ( const VECTOR2D& pt ) -> bool
1548         {
1549             m_frame->SaveCopyInUndoList( m_placeOrigin.get(), UNDO_REDO::DRILLORIGIN );
1550             DoSetDrillOrigin( getView(), m_frame, m_placeOrigin.get(), pt );
1551             return false;   // drill origin is a one-shot; don't continue with tool
1552         } );
1553 
1554     m_toolMgr->RunAction( ACTIONS::pickerTool, true, &tool );
1555 
1556     return 0;
1557 }
1558 
1559 
setTransitions()1560 void BOARD_EDITOR_CONTROL::setTransitions()
1561 {
1562     Go( &BOARD_EDITOR_CONTROL::New,                    ACTIONS::doNew.MakeEvent() );
1563     Go( &BOARD_EDITOR_CONTROL::Open,                   ACTIONS::open.MakeEvent() );
1564     Go( &BOARD_EDITOR_CONTROL::Save,                   ACTIONS::save.MakeEvent() );
1565     Go( &BOARD_EDITOR_CONTROL::SaveAs,                 ACTIONS::saveAs.MakeEvent() );
1566     Go( &BOARD_EDITOR_CONTROL::SaveCopyAs,             ACTIONS::saveCopyAs.MakeEvent() );
1567     Go( &BOARD_EDITOR_CONTROL::PageSettings,           ACTIONS::pageSettings.MakeEvent() );
1568     Go( &BOARD_EDITOR_CONTROL::Plot,                   ACTIONS::plot.MakeEvent() );
1569 
1570     Go( &BOARD_EDITOR_CONTROL::Find,                   ACTIONS::find.MakeEvent() );
1571     Go( &BOARD_EDITOR_CONTROL::FindNext,               ACTIONS::findNext.MakeEvent() );
1572 
1573     Go( &BOARD_EDITOR_CONTROL::BoardSetup,             PCB_ACTIONS::boardSetup.MakeEvent() );
1574     Go( &BOARD_EDITOR_CONTROL::ImportNetlist,          PCB_ACTIONS::importNetlist.MakeEvent() );
1575     Go( &BOARD_EDITOR_CONTROL::ImportSpecctraSession,
1576         PCB_ACTIONS::importSpecctraSession.MakeEvent() );
1577     Go( &BOARD_EDITOR_CONTROL::ExportSpecctraDSN,      PCB_ACTIONS::exportSpecctraDSN.MakeEvent() );
1578 
1579     if( ADVANCED_CFG::GetCfg().m_ShowPcbnewExportNetlist && m_frame &&
1580         m_frame->GetExportNetlistAction() )
1581         Go( &BOARD_EDITOR_CONTROL::ExportNetlist, m_frame->GetExportNetlistAction()->MakeEvent() );
1582 
1583     Go( &BOARD_EDITOR_CONTROL::GenerateDrillFiles,
1584         PCB_ACTIONS::generateDrillFiles.MakeEvent() );
1585     Go( &BOARD_EDITOR_CONTROL::GenerateFabFiles,       PCB_ACTIONS::generateGerbers.MakeEvent() );
1586     Go( &BOARD_EDITOR_CONTROL::GeneratePosFile,        PCB_ACTIONS::generatePosFile.MakeEvent() );
1587     Go( &BOARD_EDITOR_CONTROL::GenerateFabFiles,
1588         PCB_ACTIONS::generateReportFile.MakeEvent() );
1589     Go( &BOARD_EDITOR_CONTROL::GenerateFabFiles,       PCB_ACTIONS::generateD356File.MakeEvent() );
1590     Go( &BOARD_EDITOR_CONTROL::GenerateFabFiles,       PCB_ACTIONS::generateBOM.MakeEvent() );
1591 
1592     // Track & via size control
1593     Go( &BOARD_EDITOR_CONTROL::TrackWidthInc,          PCB_ACTIONS::trackWidthInc.MakeEvent() );
1594     Go( &BOARD_EDITOR_CONTROL::TrackWidthDec,          PCB_ACTIONS::trackWidthDec.MakeEvent() );
1595     Go( &BOARD_EDITOR_CONTROL::ViaSizeInc,             PCB_ACTIONS::viaSizeInc.MakeEvent() );
1596     Go( &BOARD_EDITOR_CONTROL::ViaSizeDec,             PCB_ACTIONS::viaSizeDec.MakeEvent() );
1597 
1598     // Zone actions
1599     Go( &BOARD_EDITOR_CONTROL::ZoneMerge,              PCB_ACTIONS::zoneMerge.MakeEvent() );
1600     Go( &BOARD_EDITOR_CONTROL::ZoneDuplicate,          PCB_ACTIONS::zoneDuplicate.MakeEvent() );
1601 
1602     // Placing tools
1603     Go( &BOARD_EDITOR_CONTROL::PlaceTarget,            PCB_ACTIONS::placeTarget.MakeEvent() );
1604     Go( &BOARD_EDITOR_CONTROL::PlaceFootprint,         PCB_ACTIONS::placeFootprint.MakeEvent() );
1605     Go( &BOARD_EDITOR_CONTROL::DrillOrigin,            PCB_ACTIONS::drillOrigin.MakeEvent() );
1606 
1607     Go( &BOARD_EDITOR_CONTROL::EditFpInFpEditor,       PCB_ACTIONS::editFpInFpEditor.MakeEvent() );
1608     Go( &BOARD_EDITOR_CONTROL::EditFpInFpEditor,       PCB_ACTIONS::editLibFpInFpEditor.MakeEvent() );
1609 
1610     // Other
1611     Go( &BOARD_EDITOR_CONTROL::ToggleLockSelected,     PCB_ACTIONS::toggleLock.MakeEvent() );
1612     Go( &BOARD_EDITOR_CONTROL::LockSelected,           PCB_ACTIONS::lock.MakeEvent() );
1613     Go( &BOARD_EDITOR_CONTROL::UnlockSelected,         PCB_ACTIONS::unlock.MakeEvent() );
1614 
1615     Go( &BOARD_EDITOR_CONTROL::UpdatePCBFromSchematic,
1616         ACTIONS::updatePcbFromSchematic.MakeEvent() );
1617     Go( &BOARD_EDITOR_CONTROL::UpdateSchematicFromPCB,
1618         ACTIONS::updateSchematicFromPcb.MakeEvent() );
1619     Go( &BOARD_EDITOR_CONTROL::ShowEeschema,           PCB_ACTIONS::showEeschema.MakeEvent() );
1620     Go( &BOARD_EDITOR_CONTROL::ToggleLayersManager,    PCB_ACTIONS::showLayersManager.MakeEvent() );
1621     Go( &BOARD_EDITOR_CONTROL::TogglePythonConsole,    PCB_ACTIONS::showPythonConsole.MakeEvent() );
1622     Go( &BOARD_EDITOR_CONTROL::RepairBoard,            PCB_ACTIONS::repairBoard.MakeEvent() );
1623 }
1624 
1625 
1626 const int BOARD_EDITOR_CONTROL::WIDTH_STEP = 100000;
1627