1 /*
2  * This program source code file is part of KiCad, a free EDA CAD application.
3  *
4  * Copyright (C) 2013-2017 CERN
5  * Copyright (C) 2017-2021 KiCad Developers, see AUTHORS.txt for contributors.
6  * @author Maciej Suminski <maciej.suminski@cern.ch>
7  * @author Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
8  *
9  * This program is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU General Public License
11  * as published by the Free Software Foundation; either version 2
12  * of the License, or (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, you may find one here:
21  * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
22  * or you may search the http://www.gnu.org website for the version 2 license,
23  * or you may write to the Free Software Foundation, Inc.,
24  * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
25  */
26 
27 #include <advanced_config.h>
28 #include <limits>
29 #include <board.h>
30 #include <board_design_settings.h>
31 #include <footprint.h>
32 #include <fp_shape.h>
33 #include <collectors.h>
34 #include <pcb_edit_frame.h>
35 #include <drawing_sheet/ds_proxy_view_item.h>
36 #include <kiway.h>
37 #include <array_creator.h>
38 #include <pcbnew_settings.h>
39 #include <status_popup.h>
40 #include <tool/selection_conditions.h>
41 #include <tool/tool_manager.h>
42 #include <tools/pcb_actions.h>
43 #include <tools/pcb_selection_tool.h>
44 #include <tools/edit_tool.h>
45 #include <tools/pcb_picker_tool.h>
46 #include <tools/tool_event_utils.h>
47 #include <tools/pcb_grid_helper.h>
48 #include <tools/pad_tool.h>
49 #include <view/view_controls.h>
50 #include <connectivity/connectivity_algo.h>
51 #include <connectivity/connectivity_items.h>
52 #include <bitmaps.h>
53 #include <cassert>
54 #include <functional>
55 using namespace std::placeholders;
56 #include "kicad_clipboard.h"
57 #include <wx/hyperlink.h>
58 #include <router/router_tool.h>
59 #include <dialogs/dialog_move_exact.h>
60 #include <dialogs/dialog_track_via_properties.h>
61 #include <dialogs/dialog_unit_entry.h>
62 #include <board_commit.h>
63 #include <pcb_group.h>
64 #include <pcb_target.h>
65 #include <zone_filler.h>
66 
67 
EDIT_TOOL()68 EDIT_TOOL::EDIT_TOOL() :
69         PCB_TOOL_BASE( "pcbnew.InteractiveEdit" ),
70         m_selectionTool( nullptr ),
71         m_dragging( false )
72 {
73 }
74 
75 
Reset(RESET_REASON aReason)76 void EDIT_TOOL::Reset( RESET_REASON aReason )
77 {
78     m_dragging = false;
79 
80     m_statusPopup = std::make_unique<STATUS_TEXT_POPUP>( getEditFrame<PCB_BASE_EDIT_FRAME>() );
81 
82     if( aReason != RUN )
83         m_commit.reset( new BOARD_COMMIT( this ) );
84 }
85 
86 
SPECIAL_TOOLS_CONTEXT_MENU(TOOL_INTERACTIVE * aTool)87 SPECIAL_TOOLS_CONTEXT_MENU::SPECIAL_TOOLS_CONTEXT_MENU( TOOL_INTERACTIVE* aTool ) :
88         CONDITIONAL_MENU( aTool )
89 {
90     SetIcon( BITMAPS::special_tools );
91     SetTitle( _( "Special Tools" ) );
92 
93     AddItem( PCB_ACTIONS::moveExact, SELECTION_CONDITIONS::ShowAlways );
94     AddItem( PCB_ACTIONS::moveWithReference, SELECTION_CONDITIONS::ShowAlways );
95     AddItem( PCB_ACTIONS::positionRelative, SELECTION_CONDITIONS::ShowAlways );
96     AddItem( PCB_ACTIONS::createArray, SELECTION_CONDITIONS::ShowAlways );
97 }
98 
99 
Init()100 bool EDIT_TOOL::Init()
101 {
102     // Find the selection tool, so they can cooperate
103     m_selectionTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
104 
105     auto propertiesCondition =
106             [&]( const SELECTION& aSel )
107             {
108                 if( aSel.GetSize() == 0 )
109                 {
110                     if( getView()->IsLayerVisible( LAYER_SCHEMATIC_DRAWINGSHEET ) )
111                     {
112                         DS_PROXY_VIEW_ITEM* ds = frame()->GetCanvas()->GetDrawingSheet();
113                         VECTOR2D            cursor = getViewControls()->GetCursorPosition( false );
114 
115                         if( ds && ds->HitTestDrawingSheetItems( getView(), (wxPoint) cursor ) )
116                             return true;
117                     }
118 
119                     return false;
120                 }
121 
122                 if( aSel.GetSize() == 1 )
123                     return true;
124 
125                 for( EDA_ITEM* item : aSel )
126                 {
127                     if( !dynamic_cast<PCB_TRACK*>( item ) )
128                         return false;
129                 }
130 
131                 return true;
132             };
133 
134     auto inFootprintEditor =
135             [ this ]( const SELECTION& aSelection )
136             {
137                 return m_isFootprintEditor;
138             };
139 
140     auto singleFootprintCondition = SELECTION_CONDITIONS::OnlyType( PCB_FOOTPRINT_T )
141                                         && SELECTION_CONDITIONS::Count( 1 );
142 
143     auto noActiveToolCondition =
144             [ this ]( const SELECTION& aSelection )
145             {
146                 return frame()->ToolStackIsEmpty();
147             };
148 
149     auto notMovingCondition =
150             [ this ]( const SELECTION& aSelection )
151             {
152                 return !frame()->IsCurrentTool( PCB_ACTIONS::move )
153                        && !frame()->IsCurrentTool( PCB_ACTIONS::moveWithReference );
154             };
155 
156     auto noItemsCondition =
157             [ this ]( const SELECTION& aSelections ) -> bool
158             {
159                 return frame()->GetBoard() && !frame()->GetBoard()->IsEmpty();
160             };
161 
162     // Add context menu entries that are displayed when selection tool is active
163     CONDITIONAL_MENU& menu = m_selectionTool->GetToolMenu().GetMenu();
164 
165     menu.AddItem( PCB_ACTIONS::move,              SELECTION_CONDITIONS::NotEmpty
166                                                       && notMovingCondition );
167     menu.AddItem( PCB_ACTIONS::breakTrack,        SELECTION_CONDITIONS::Count( 1 )
168                                                       && SELECTION_CONDITIONS::OnlyTypes( GENERAL_COLLECTOR::Tracks ) );
169     menu.AddItem( PCB_ACTIONS::drag45Degree,      SELECTION_CONDITIONS::Count( 1 )
170                                                       && SELECTION_CONDITIONS::OnlyTypes( GENERAL_COLLECTOR::DraggableItems ) );
171     menu.AddItem( PCB_ACTIONS::dragFreeAngle,     SELECTION_CONDITIONS::Count( 1 )
172                                                       && SELECTION_CONDITIONS::OnlyTypes( GENERAL_COLLECTOR::DraggableItems )
173                                                       && !SELECTION_CONDITIONS::OnlyType( PCB_FOOTPRINT_T ) );
174     menu.AddItem( PCB_ACTIONS::filletTracks,      SELECTION_CONDITIONS::OnlyTypes( GENERAL_COLLECTOR::Tracks ) );
175     menu.AddItem( PCB_ACTIONS::rotateCcw,         SELECTION_CONDITIONS::NotEmpty );
176     menu.AddItem( PCB_ACTIONS::rotateCw,          SELECTION_CONDITIONS::NotEmpty );
177     menu.AddItem( PCB_ACTIONS::flip,              SELECTION_CONDITIONS::NotEmpty );
178     menu.AddItem( PCB_ACTIONS::mirror,            inFootprintEditor && SELECTION_CONDITIONS::NotEmpty );
179 
180     menu.AddItem( PCB_ACTIONS::properties,        propertiesCondition );
181 
182     // Footprint actions
183     menu.AddSeparator();
184     menu.AddItem( PCB_ACTIONS::editFpInFpEditor,  singleFootprintCondition );
185     menu.AddItem( PCB_ACTIONS::updateFootprint,   singleFootprintCondition );
186     menu.AddItem( PCB_ACTIONS::changeFootprint,   singleFootprintCondition );
187 
188     // Add the submenu for create array and special move
189     auto specialToolsSubMenu = std::make_shared<SPECIAL_TOOLS_CONTEXT_MENU>( this );
190     menu.AddSeparator();
191     m_selectionTool->GetToolMenu().AddSubMenu( specialToolsSubMenu );
192     menu.AddMenu( specialToolsSubMenu.get(),      SELECTION_CONDITIONS::NotEmpty, 100 );
193 
194     menu.AddSeparator( 150 );
195     menu.AddItem( ACTIONS::cut,                   SELECTION_CONDITIONS::NotEmpty, 150 );
196     menu.AddItem( ACTIONS::copy,                  SELECTION_CONDITIONS::NotEmpty, 150 );
197 
198     // Selection tool handles the context menu for some other tools, such as the Picker.
199     // Don't add things like Paste when another tool is active.
200     menu.AddItem( ACTIONS::paste,                 noActiveToolCondition, 150 );
201     menu.AddItem( ACTIONS::pasteSpecial,          noActiveToolCondition && !inFootprintEditor, 150 );
202     menu.AddItem( ACTIONS::duplicate,             SELECTION_CONDITIONS::NotEmpty, 150 );
203     menu.AddItem( ACTIONS::doDelete,              SELECTION_CONDITIONS::NotEmpty, 150 );
204 
205     menu.AddSeparator( 150 );
206     menu.AddItem( ACTIONS::selectAll, noItemsCondition, 150 );
207 
208     return true;
209 }
210 
211 
GetAndPlace(const TOOL_EVENT & aEvent)212 int EDIT_TOOL::GetAndPlace( const TOOL_EVENT& aEvent )
213 {
214     // GetAndPlace makes sense only in board editor, although it is also called
215     // in fpeditor, that shares the same EDIT_TOOL list
216     if( !getEditFrame<PCB_BASE_FRAME>()->IsType( FRAME_PCB_EDITOR ) )
217         return 0;
218 
219     PCB_SELECTION_TOOL* selectionTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
220     FOOTPRINT*          fp = getEditFrame<PCB_BASE_FRAME>()->GetFootprintFromBoardByReference();
221 
222     if( fp )
223     {
224         m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
225         m_toolMgr->RunAction( PCB_ACTIONS::selectItem, true, (void*) fp );
226 
227         selectionTool->GetSelection().SetReferencePoint( fp->GetPosition() );
228         m_toolMgr->RunAction( PCB_ACTIONS::move, false );
229     }
230 
231     return 0;
232 }
233 
234 
invokeInlineRouter(int aDragMode)235 bool EDIT_TOOL::invokeInlineRouter( int aDragMode )
236 {
237     ROUTER_TOOL* theRouter = m_toolMgr->GetTool<ROUTER_TOOL>();
238 
239     if( !theRouter )
240         return false;
241 
242     // don't allow switch from moving to dragging
243     if( m_dragging )
244     {
245         wxBell();
246         return false;
247     }
248 
249     // make sure we don't accidentally invoke inline routing mode while the router is already
250     // active!
251     if( theRouter->IsToolActive() )
252         return false;
253 
254     if( theRouter->CanInlineDrag( aDragMode ) )
255     {
256         m_toolMgr->RunAction( PCB_ACTIONS::routerInlineDrag, true,
257                               static_cast<intptr_t>( aDragMode ) );
258         return true;
259     }
260 
261     return false;
262 }
263 
264 
265 
isRouterActive() const266 bool EDIT_TOOL::isRouterActive() const
267 {
268     ROUTER_TOOL* router = m_toolMgr->GetTool<ROUTER_TOOL>();
269 
270     return router && router->IsToolActive();
271 }
272 
273 
Drag(const TOOL_EVENT & aEvent)274 int EDIT_TOOL::Drag( const TOOL_EVENT& aEvent )
275 {
276     if( !m_toolMgr->GetTool<ROUTER_TOOL>() )
277         return false; // don't drag when no router tool (i.e. fp editor)
278 
279     if( m_toolMgr->GetTool<ROUTER_TOOL>()->IsToolActive() )
280         return false; // don't drag when router is already active
281 
282     int mode = PNS::DM_ANY;
283 
284     if( aEvent.IsAction( &PCB_ACTIONS::dragFreeAngle ) )
285         mode |= PNS::DM_FREE_ANGLE;
286 
287     PCB_SELECTION& selection = m_selectionTool->RequestSelection(
288             []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
289             {
290                 sTool->FilterCollectorForFreePads( aCollector );
291                 sTool->FilterCollectorForHierarchy( aCollector, true );
292             },
293             true /* prompt user regarding locked items */ );
294 
295     if( selection.Empty() )
296         return 0;
297 
298     if( selection.Size() == 1 && selection.Front()->Type() == PCB_ARC_T )
299     {
300         // TODO: This really should be done in PNS to ensure DRC is maintained, but for now
301         // it allows interactive editing of an arc track
302         return DragArcTrack( aEvent );
303     }
304     else
305     {
306         invokeInlineRouter( mode );
307     }
308 
309     return 0;
310 }
311 
312 
DragArcTrack(const TOOL_EVENT & aEvent)313 int EDIT_TOOL::DragArcTrack( const TOOL_EVENT& aEvent )
314 {
315     PCB_SELECTION& selection = m_selectionTool->GetSelection();
316 
317     if( selection.Size() != 1 || selection.Front()->Type() != PCB_ARC_T )
318         return 0;
319 
320     PCB_ARC* theArc = static_cast<PCB_ARC*>( selection.Front() );
321     double   arcAngleDegrees = std::abs( theArc->GetAngle() ) / 10.0;
322 
323     if( arcAngleDegrees + ADVANCED_CFG::GetCfg().m_MaxTangentAngleDeviation >= 180.0 )
324     {
325         frame()->ShowInfoBarError(
326                 wxString::Format( _( "Unable to resize arc tracks %.1f degrees or greater." ),
327                                   180.0 - ADVANCED_CFG::GetCfg().m_MaxTangentAngleDeviation ) );
328         return 0; // don't bother with > 180 degree arcs
329     }
330 
331     KIGFX::VIEW_CONTROLS* controls = getViewControls();
332 
333     Activate();
334     // Must be done after Activate() so that it gets set into the correct context
335     controls->ShowCursor( true );
336     controls->SetAutoPan( true );
337 
338     bool     restore_state = false;
339     VECTOR2I arcCenter = theArc->GetCenter();
340     SEG      tanStart = SEG( arcCenter, theArc->GetStart() ).PerpendicularSeg( theArc->GetStart() );
341     SEG      tanEnd = SEG( arcCenter, theArc->GetEnd() ).PerpendicularSeg( theArc->GetEnd() );
342 
343     //Ensure the tangent segments are in the correct orientation
344     VECTOR2I tanIntersect = tanStart.IntersectLines( tanEnd ).get();
345     tanStart.A = tanIntersect;
346     tanStart.B = theArc->GetStart();
347     tanEnd.A = tanIntersect;
348     tanEnd.B = theArc->GetEnd();
349 
350     KICAD_T track_types[] = { PCB_PAD_T, PCB_VIA_T, PCB_TRACE_T, PCB_ARC_T, EOT };
351 
352     auto getUniqueTrackAtAnchorCollinear =
353         [&]( const VECTOR2I& aAnchor, const SEG& aCollinearSeg ) -> PCB_TRACK*
354         {
355             auto conn = board()->GetConnectivity();
356 
357             // Allow items at a distance within the width of the arc track
358             int allowedDeviation = theArc->GetWidth();
359 
360             std::vector<BOARD_CONNECTED_ITEM*> itemsOnAnchor;
361 
362             for( int i = 0; i < 3; i++ )
363             {
364                 itemsOnAnchor = conn->GetConnectedItemsAtAnchor( theArc, aAnchor, track_types,
365                                                                  allowedDeviation );
366                 allowedDeviation /= 2;
367 
368                 if( itemsOnAnchor.size() == 1 )
369                     break;
370             }
371 
372             PCB_TRACK* retval = nullptr;
373 
374             if( itemsOnAnchor.size() == 1 && itemsOnAnchor.front()->Type() == PCB_TRACE_T )
375             {
376                 retval = static_cast<PCB_TRACK*>( itemsOnAnchor.front() );
377                 SEG trackSeg( retval->GetStart(), retval->GetEnd() );
378 
379                 // Allow deviations in colinearity as defined in ADVANCED_CFG
380                 if( trackSeg.AngleDegrees( aCollinearSeg )
381                     > ADVANCED_CFG::GetCfg().m_MaxTangentAngleDeviation )
382                 {
383                     retval = nullptr;
384                 }
385             }
386 
387             if( !retval )
388             {
389                 retval = new PCB_TRACK( theArc->GetParent() );
390                 retval->SetStart( (wxPoint) aAnchor );
391                 retval->SetEnd( (wxPoint) aAnchor );
392                 retval->SetNet( theArc->GetNet() );
393                 retval->SetLayer( theArc->GetLayer() );
394                 retval->SetWidth( theArc->GetWidth() );
395                 retval->SetLocked( theArc->IsLocked() );
396                 retval->SetFlags( IS_NEW );
397                 getView()->Add( retval );
398             }
399 
400             return retval;
401         };
402 
403     PCB_TRACK* trackOnStart = getUniqueTrackAtAnchorCollinear( theArc->GetStart(), tanStart);
404     PCB_TRACK* trackOnEnd = getUniqueTrackAtAnchorCollinear( theArc->GetEnd(), tanEnd );
405 
406     // Make copies of items to be edited
407     PCB_ARC*   theArcCopy = new PCB_ARC( *theArc );
408     PCB_TRACK* trackOnStartCopy = new PCB_TRACK( *trackOnStart );
409     PCB_TRACK* trackOnEndCopy = new PCB_TRACK( *trackOnEnd );
410 
411     if( trackOnStart->GetLength() != 0 )
412     {
413         tanStart.A = trackOnStart->GetStart();
414         tanStart.B = trackOnStart->GetEnd();
415     }
416 
417     if( trackOnEnd->GetLength() != 0 )
418     {
419         tanEnd.A = trackOnEnd->GetStart();
420         tanEnd.B = trackOnEnd->GetEnd();
421     }
422 
423     // Recalculate intersection point
424     tanIntersect = tanStart.IntersectLines( tanEnd ).get();
425 
426     auto isTrackStartClosestToArcStart =
427         [&]( PCB_TRACK* aTrack ) -> bool
428         {
429             double trackStartToArcStart = GetLineLength( aTrack->GetStart(), theArc->GetStart() );
430             double trackEndToArcStart = GetLineLength( aTrack->GetEnd(), theArc->GetStart() );
431 
432             return trackStartToArcStart < trackEndToArcStart;
433         };
434 
435     bool isStartTrackOnStartPt = isTrackStartClosestToArcStart( trackOnStart );
436     bool isEndTrackOnStartPt = isTrackStartClosestToArcStart( trackOnEnd );
437 
438     // Calculate constraints
439     //======================
440     // maxTanCircle is the circle with maximum radius that is tangent to the two adjacent straight
441     // tracks and whose tangent points are constrained within the original tracks and their
442     // projected intersection points.
443     //
444     // The cursor will be constrained first within the isosceles triangle formed by the segments
445     // cSegTanStart, cSegTanEnd and cSegChord. After that it will be constrained to be outside
446     // maxTanCircle.
447     //
448     //
449     //                   ____________  <-cSegTanStart
450     //                  /     *   . '   *
451     //    cSegTanEnd-> /  *   . '           *
452     //                /*  . ' <-cSegChord     *
453     //               /. '
454     //              /*                           *
455     //
456     //              *             c               *  <-maxTanCircle
457     //
458     //               *                           *
459     //
460     //                  *                     *
461     //                    *                 *
462     //                        *        *
463     //
464 
465     auto getFurthestPointToTanInterstect =
466             [&]( VECTOR2I& aPointA, VECTOR2I& aPointB ) -> VECTOR2I
467             {
468                 if( ( aPointA - tanIntersect ).EuclideanNorm()
469                     > ( aPointB - tanIntersect ).EuclideanNorm() )
470                 {
471                     return aPointA;
472                 }
473                 else
474                 {
475                     return aPointB;
476                 }
477             };
478 
479     CIRCLE   maxTanCircle;
480     VECTOR2I tanStartPoint = getFurthestPointToTanInterstect( tanStart.A, tanStart.B );
481     VECTOR2I tanEndPoint = getFurthestPointToTanInterstect( tanEnd.A, tanEnd.B );
482     VECTOR2I tempTangentPoint = tanEndPoint;
483 
484     if( getFurthestPointToTanInterstect( tanStartPoint, tanEndPoint ) == tanEndPoint )
485         tempTangentPoint = tanStartPoint;
486 
487     maxTanCircle.ConstructFromTanTanPt( tanStart, tanEnd, tempTangentPoint );
488     VECTOR2I maxTanPtStart = tanStart.LineProject( maxTanCircle.Center );
489     VECTOR2I maxTanPtEnd = tanEnd.LineProject( maxTanCircle.Center );
490 
491     SEG cSegTanStart( maxTanPtStart, tanIntersect );
492     SEG cSegTanEnd( maxTanPtEnd, tanIntersect );
493     SEG cSegChord( maxTanPtStart, maxTanPtEnd );
494 
495     int cSegTanStartSide = cSegTanStart.Side( theArc->GetMid() );
496     int cSegTanEndSide = cSegTanEnd.Side( theArc->GetMid() );
497     int cSegChordSide = cSegChord.Side( theArc->GetMid() );
498 
499     bool eatFirstMouseUp = true;
500 
501     // Start the tool loop
502     while( TOOL_EVENT* evt = Wait() )
503     {
504         m_cursor = controls->GetMousePosition();
505 
506         // Constrain cursor within the isosceles triangle
507         if( cSegTanStartSide != cSegTanStart.Side( m_cursor )
508             || cSegTanEndSide != cSegTanEnd.Side( m_cursor )
509             || cSegChordSide != cSegChord.Side( m_cursor ) )
510         {
511             std::vector<VECTOR2I> possiblePoints;
512 
513             possiblePoints.push_back( cSegTanEnd.NearestPoint( m_cursor ) );
514             possiblePoints.push_back( cSegChord.NearestPoint( m_cursor ) );
515 
516             VECTOR2I closest = cSegTanStart.NearestPoint( m_cursor );
517 
518             for( VECTOR2I candidate : possiblePoints )
519             {
520                 if( ( candidate - m_cursor ).SquaredEuclideanNorm()
521                     < ( closest - m_cursor ).SquaredEuclideanNorm() )
522                 {
523                     closest = candidate;
524                 }
525             }
526 
527             m_cursor = closest;
528         }
529 
530         // Constrain cursor to be outside maxTanCircle
531         if( ( m_cursor - maxTanCircle.Center ).EuclideanNorm() < maxTanCircle.Radius )
532             m_cursor = maxTanCircle.NearestPoint( m_cursor );
533 
534         controls->ForceCursorPosition( true, m_cursor );
535 
536         // Calculate resulting object coordinates
537         CIRCLE circlehelper;
538         circlehelper.ConstructFromTanTanPt( cSegTanStart, cSegTanEnd, m_cursor );
539 
540         VECTOR2I newCenter = circlehelper.Center;
541         VECTOR2I newStart = cSegTanStart.LineProject( newCenter );
542         VECTOR2I newEnd = cSegTanEnd.LineProject( newCenter );
543         VECTOR2I newMid = CalcArcMid( newStart, newEnd, newCenter );
544 
545         // Update objects
546         theArc->SetStart( (wxPoint) newStart );
547         theArc->SetEnd( (wxPoint) newEnd );
548         theArc->SetMid( (wxPoint) newMid );
549 
550         if( isStartTrackOnStartPt )
551             trackOnStart->SetStart( (wxPoint) newStart );
552         else
553             trackOnStart->SetEnd( (wxPoint) newStart );
554 
555         if( isEndTrackOnStartPt )
556             trackOnEnd->SetStart( (wxPoint) newEnd );
557         else
558             trackOnEnd->SetEnd( (wxPoint) newEnd );
559 
560         // Update view
561         getView()->Update( trackOnStart );
562         getView()->Update( trackOnEnd );
563         getView()->Update( theArc );
564 
565         // Handle events
566         if( evt->IsMotion() || evt->IsDrag( BUT_LEFT ) )
567         {
568             eatFirstMouseUp = false;
569         }
570         else if( evt->IsCancelInteractive() || evt->IsActivate() )
571         {
572             restore_state = true; // Canceling the tool means that items have to be restored
573             break;                // Finish
574         }
575         else if( evt->IsAction( &ACTIONS::undo ) )
576         {
577             restore_state = true; // Perform undo locally
578             break;                // Finish
579         }
580         else if( evt->IsMouseUp( BUT_LEFT ) || evt->IsClick( BUT_LEFT )
581                 || evt->IsDblClick( BUT_LEFT ) )
582         {
583             // Eat mouse-up/-click events that leaked through from the lock dialog
584             if( eatFirstMouseUp && evt->Parameter<intptr_t>() != ACTIONS::CURSOR_CLICK )
585             {
586                 eatFirstMouseUp = false;
587                 continue;
588             }
589 
590             break; // Finish
591         }
592     }
593 
594     // Ensure we only do one commit operation on each object
595     auto processTrack =
596         [&]( PCB_TRACK* aTrack, PCB_TRACK* aTrackCopy, int aMaxLengthIU ) -> bool
597         {
598             if( aTrack->IsNew() )
599             {
600                 getView()->Remove( aTrack );
601 
602                 if( aTrack->GetLength() <= aMaxLengthIU )
603                 {
604                     delete aTrack;
605                     delete aTrackCopy;
606                     aTrack = nullptr;
607                     aTrackCopy = nullptr;
608                     return false;
609                 }
610                 else
611                 {
612                     m_commit->Add( aTrack );
613                     delete aTrackCopy;
614                     aTrackCopy = nullptr;
615                     return true;
616                 }
617             }
618             else if( aTrack->GetLength() <= aMaxLengthIU )
619             {
620                 aTrack->SwapData( aTrackCopy ); //restore the original before notifying COMMIT
621                 m_commit->Remove( aTrack );
622                 delete aTrackCopy;
623                 aTrackCopy = nullptr;
624                 return false;
625             }
626             else
627             {
628                 m_commit->Modified( aTrack, aTrackCopy );
629             }
630 
631             return true;
632         };
633 
634     // Amend the end points of the arc if we delete the joining tracks
635     wxPoint newStart = trackOnStart->GetStart();
636     wxPoint newEnd = trackOnEnd->GetStart();
637 
638     if( isStartTrackOnStartPt )
639         newStart = trackOnStart->GetEnd();
640 
641     if( isEndTrackOnStartPt )
642         newEnd = trackOnEnd->GetEnd();
643 
644     int maxLengthIU = KiROUND( ADVANCED_CFG::GetCfg().m_MaxTrackLengthToKeep * IU_PER_MM );
645 
646     if( !processTrack( trackOnStart, trackOnStartCopy, maxLengthIU ) )
647         theArc->SetStart( newStart );
648 
649     if( !processTrack( trackOnEnd, trackOnEndCopy, maxLengthIU ) )
650         theArc->SetEnd( newEnd );
651 
652     processTrack( theArc, theArcCopy, 0 ); // only delete the arc if start and end points coincide
653 
654     // Should we commit?
655     if( restore_state )
656         m_commit->Revert();
657     else
658         m_commit->Push( _( "Drag Arc Track" ) );
659 
660     return 0;
661 }
662 
663 
Move(const TOOL_EVENT & aEvent)664 int EDIT_TOOL::Move( const TOOL_EVENT& aEvent )
665 {
666     if( isRouterActive() )
667     {
668         wxBell();
669         return 0;
670     }
671 
672     return doMoveSelection( aEvent );
673 }
674 
675 
MoveWithReference(const TOOL_EVENT & aEvent)676 int EDIT_TOOL::MoveWithReference( const TOOL_EVENT& aEvent )
677 {
678     if( isRouterActive() )
679     {
680         wxBell();
681         return 0;
682     }
683 
684     return doMoveSelection( aEvent, true );
685 }
686 
687 
688 // Note: aEvent MUST NOT be const&; the source will get de-allocated if we go into the picker's
689 // event loop.
doMoveSelection(TOOL_EVENT aEvent,bool aPickReference)690 int EDIT_TOOL::doMoveSelection( TOOL_EVENT aEvent, bool aPickReference )
691 {
692     PCB_BASE_EDIT_FRAME*  editFrame = getEditFrame<PCB_BASE_EDIT_FRAME>();
693     KIGFX::VIEW_CONTROLS* controls  = getViewControls();
694     VECTOR2I              originalCursorPos = controls->GetCursorPosition();
695 
696     // Be sure that there is at least one item that we can modify. If nothing was selected before,
697     // try looking for the stuff under mouse cursor (i.e. KiCad old-style hover selection)
698     PCB_SELECTION& selection = m_selectionTool->RequestSelection(
699             []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
700             {
701                 sTool->FilterCollectorForMarkers( aCollector );
702             },
703             // Prompt user regarding locked items if in board editor and in free-pad-mode (if
704             // we're not in free-pad mode we delay this until the second RequestSelection()).
705             frame()->Settings().m_AllowFreePads && !m_isFootprintEditor );
706 
707     if( m_dragging || selection.Empty() )
708         return 0;
709 
710     LSET     item_layers = selection.GetSelectionLayers();
711     bool     is_hover    = selection.IsHover(); // N.B. This must be saved before the second call
712                                                 // to RequestSelection() below
713     VECTOR2I pickedReferencePoint;
714 
715     // Now filter out pads if not in free pads mode.  We cannot do this in the first
716     // RequestSelection() as we need the item_layers when a pad is the selection front.
717     if( !m_isFootprintEditor && !frame()->Settings().m_AllowFreePads )
718     {
719         selection = m_selectionTool->RequestSelection(
720                 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
721                 {
722                     sTool->FilterCollectorForMarkers( aCollector );
723                     sTool->FilterCollectorForFreePads( aCollector );
724                 },
725                 true /* prompt user regarding locked items */ );
726     }
727 
728     if( selection.Empty() )
729         return 0;
730 
731     std::string tool = aEvent.GetCommandStr().get();
732     editFrame->PushTool( tool );
733 
734     Activate();
735     // Must be done after Activate() so that it gets set into the correct context
736     controls->ShowCursor( true );
737     controls->SetAutoPan( true );
738 
739     if( aPickReference && !pickReferencePoint( _( "Select reference point for move..." ), "", "",
740                                                pickedReferencePoint ) )
741     {
742         if( is_hover )
743             m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
744 
745         editFrame->PopTool( tool );
746         return 0;
747     }
748 
749     std::vector<BOARD_ITEM*> sel_items;     // All the items operated on by the move below
750     std::vector<BOARD_ITEM*> orig_items;    // All the original items in the selection
751 
752     for( EDA_ITEM* item : selection )
753     {
754         BOARD_ITEM* boardItem = dynamic_cast<BOARD_ITEM*>( item );
755         FOOTPRINT*  footprint = dynamic_cast<FOOTPRINT*>( item );
756 
757         if( boardItem )
758         {
759             orig_items.push_back( boardItem );
760             sel_items.push_back( boardItem );
761         }
762 
763         if( footprint )
764         {
765             for( PAD* pad : footprint->Pads() )
766                 sel_items.push_back( pad );
767 
768             // Clear this flag here; it will be set by the netlist updater if the footprint is new
769             // so that it was skipped in the initial connectivity update in OnNetlistChanged
770             footprint->SetAttributes( footprint->GetAttributes() & ~FP_JUST_ADDED );
771         }
772     }
773 
774     bool        restore_state = false;
775     VECTOR2I    originalPos;
776     VECTOR2I    totalMovement;
777     PCB_GRID_HELPER grid( m_toolMgr, editFrame->GetMagneticItemsSettings() );
778     TOOL_EVENT* evt = &aEvent;
779     VECTOR2I    prevPos;
780 
781     bool lock45          = false;
782     bool eatFirstMouseUp = true;
783     bool hasRedrawn3D    = false;
784     bool allowRedraw3D   = editFrame->GetDisplayOptions().m_Live3DRefresh;
785 
786     // Prime the pump
787     m_toolMgr->RunAction( ACTIONS::refreshPreview );
788 
789     // Main loop: keep receiving events
790     do
791     {
792         VECTOR2I movement;
793         editFrame->GetCanvas()->SetCurrentCursor( KICURSOR::MOVING );
794         grid.SetSnap( !evt->Modifier( MD_SHIFT ) );
795         grid.SetUseGrid( getView()->GetGAL()->GetGridSnapping() && !evt->DisableGridSnapping() );
796 
797         if( evt->IsMotion() || evt->IsDrag( BUT_LEFT ) )
798             eatFirstMouseUp = false;
799 
800         if( evt->IsAction( &PCB_ACTIONS::move ) || evt->IsMotion() || evt->IsDrag( BUT_LEFT )
801                 || evt->IsAction( &ACTIONS::refreshPreview )
802                 || evt->IsAction( &PCB_ACTIONS::moveWithReference ) )
803         {
804             if( m_dragging && evt->Category() == TC_MOUSE )
805             {
806                 bool redraw3D = false;
807 
808                 VECTOR2I mousePos( controls->GetMousePosition() );
809 
810                 m_cursor = grid.BestSnapAnchor( mousePos, item_layers, sel_items );
811 
812                 if( controls->GetSettings().m_lastKeyboardCursorPositionValid )
813                 {
814                     long action = controls->GetSettings().m_lastKeyboardCursorCommand;
815 
816                     // The arrow keys are by definition SINGLE AXIS.  Do not allow the other
817                     // axis to be snapped to the grid.
818                     if( action == ACTIONS::CURSOR_LEFT || action == ACTIONS::CURSOR_RIGHT )
819                         m_cursor.y = prevPos.y;
820                     else if( action == ACTIONS::CURSOR_UP || action == ACTIONS::CURSOR_DOWN )
821                         m_cursor.x = prevPos.x;
822                 }
823 
824                 if( !selection.HasReferencePoint() )
825                     originalPos = m_cursor;
826 
827                 if( lock45 )
828                 {
829                     VECTOR2I moveVector = m_cursor - originalPos;
830                     m_cursor = originalPos + GetVectorSnapped45( moveVector );
831                 }
832 
833                 controls->ForceCursorPosition( true, m_cursor );
834                 selection.SetReferencePoint( m_cursor );
835 
836                 movement = m_cursor - prevPos;
837                 prevPos = m_cursor;
838                 totalMovement += movement;
839 
840                 // Drag items to the current cursor position
841                 for( EDA_ITEM* item : sel_items )
842                 {
843                     // Don't double move footprint pads, fields, etc.
844                     //
845                     // For PCB_GROUP_T, we make sure the selection includes only the top level
846                     // group and not its descendants.
847                     if( !item->GetParent() || !item->GetParent()->IsSelected() )
848                         static_cast<BOARD_ITEM*>( item )->Move( movement );
849 
850                     if( !redraw3D && item->Type() == PCB_FOOTPRINT_T )
851                         redraw3D = true;
852                 }
853 
854                 if( redraw3D && allowRedraw3D )
855                 {
856                     editFrame->Update3DView( false, true );
857                     hasRedrawn3D = true;
858                 }
859 
860                 m_toolMgr->PostEvent( EVENTS::SelectedItemsMoved );
861             }
862             else if( !m_dragging && !evt->IsAction( &ACTIONS::refreshPreview ) )
863             {
864                 // Prepare to start dragging
865                 if( !( evt->IsAction( &PCB_ACTIONS::move )
866                        || evt->IsAction( &PCB_ACTIONS::moveWithReference ) )
867                     && ( editFrame->Settings().m_TrackDragAction != TRACK_DRAG_ACTION::MOVE ) )
868                 {
869                     if( invokeInlineRouter( PNS::DM_ANY ) )
870                         break;
871                 }
872 
873                 m_dragging = true;
874 
875                 // When editing footprints, all items have the same parent
876                 if( IsFootprintEditor() )
877                 {
878                     m_commit->Modify( selection.Front() );
879                 }
880                 else
881                 {
882                     // Save items, so changes can be undone
883                     for( EDA_ITEM* item : selection )
884                     {
885                         // Don't double move footprint pads, fields, etc.
886                         //
887                         // For PCB_GROUP_T, the parent is the board.
888                         if( item->GetParent() && item->GetParent()->IsSelected() )
889                             continue;
890 
891                         m_commit->Modify( item );
892 
893                         // If moving a group, record position of all the descendants for undo
894                         if( item->Type() == PCB_GROUP_T )
895                         {
896                             PCB_GROUP* group = static_cast<PCB_GROUP*>( item );
897                             group->RunOnDescendants( [&]( BOARD_ITEM* bItem )
898                                                      {
899                                                          m_commit->Modify( bItem );
900                                                      });
901                         }
902                     }
903                 }
904 
905                 editFrame->UndoRedoBlock( true );
906                 m_cursor = controls->GetCursorPosition();
907 
908                 if( selection.HasReferencePoint() )
909                 {
910                     // start moving with the reference point attached to the cursor
911                     grid.SetAuxAxes( false );
912 
913                     if( lock45 )
914                     {
915                         VECTOR2I moveVector = m_cursor - originalPos;
916                         m_cursor = originalPos + GetVectorSnapped45( moveVector );
917                     }
918 
919                     movement = m_cursor - selection.GetReferencePoint();
920 
921                     // Drag items to the current cursor position
922                     for( EDA_ITEM* item : selection )
923                     {
924                         // Don't double move footprint pads, fields, etc.
925                         if( item->GetParent() && item->GetParent()->IsSelected() )
926                             continue;
927 
928                         static_cast<BOARD_ITEM*>( item )->Move( movement );
929                     }
930 
931                     selection.SetReferencePoint( m_cursor );
932                 }
933                 else
934                 {
935                     std::vector<BOARD_ITEM*> items;
936 
937                     for( EDA_ITEM* item : selection )
938                         items.push_back( static_cast<BOARD_ITEM*>( item ) );
939 
940                     m_cursor = grid.BestDragOrigin( originalCursorPos, items );
941 
942                     // Set the current cursor position to the first dragged item origin, so the
943                     // movement vector could be computed later
944                     if( aPickReference )
945                     {
946                         selection.SetReferencePoint( pickedReferencePoint );
947                         controls->ForceCursorPosition( true, pickedReferencePoint );
948                         m_cursor = pickedReferencePoint;
949                     }
950                     else
951                     {
952                         // Check if user wants to warp the mouse to origin of moved object
953                         if( !editFrame->GetMoveWarpsCursor() )
954                             m_cursor = originalCursorPos; // No, so use original mouse pos instead
955 
956                         selection.SetReferencePoint( m_cursor );
957                         grid.SetAuxAxes( true, m_cursor );
958                     }
959 
960                     originalPos = m_cursor;
961 
962                 }
963 
964                 controls->SetCursorPosition( m_cursor, false );
965 
966                 prevPos = m_cursor;
967                 controls->SetAutoPan( true );
968                 m_toolMgr->PostEvent( EVENTS::SelectedItemsModified );
969             }
970 
971             m_toolMgr->RunAction( PCB_ACTIONS::updateLocalRatsnest, false,
972                                   new VECTOR2I( movement ) );
973         }
974         else if( evt->IsCancelInteractive() || evt->IsActivate() )
975         {
976             if( m_dragging && evt->IsCancelInteractive() )
977                 evt->SetPassEvent( false );
978 
979             restore_state = true; // Canceling the tool means that items have to be restored
980             break;                // Finish
981         }
982         else if( evt->IsAction( &ACTIONS::undo ) )
983         {
984             restore_state = true; // Perform undo locally
985             break;                // Finish
986         }
987         else if( evt->IsAction( &ACTIONS::doDelete ) || evt->IsAction( &ACTIONS::cut ) )
988         {
989             // Dispatch TOOL_ACTIONs
990             evt->SetPassEvent();
991             break; // finish -- there is no further processing for removed items
992         }
993         else if( evt->IsAction( &ACTIONS::duplicate ) )
994         {
995             evt->SetPassEvent();
996             break; // finish -- Duplicate tool will start a new Move with the dup'ed items
997         }
998         else if( evt->IsAction( &PCB_ACTIONS::rotateCw )
999                 || evt->IsAction( &PCB_ACTIONS::rotateCcw )
1000                 || evt->IsAction( &PCB_ACTIONS::flip )
1001                 || evt->IsAction( &PCB_ACTIONS::mirror ) )
1002         {
1003             eatFirstMouseUp = false;
1004             evt->SetPassEvent();
1005         }
1006         else if( evt->IsAction( &PCB_ACTIONS::moveExact ) )
1007         {
1008             // Reset positions so the Move Exactly is from the start.
1009             for( EDA_ITEM* item : selection )
1010             {
1011                 BOARD_ITEM* i = static_cast<BOARD_ITEM*>( item );
1012                 i->Move( -totalMovement );
1013             }
1014 
1015             break; // finish -- we moved exactly, so we are finished
1016         }
1017         else if( evt->IsMouseUp( BUT_LEFT ) || evt->IsClick( BUT_LEFT ) )
1018         {
1019             // Eat mouse-up/-click events that leaked through from the lock dialog
1020             if( eatFirstMouseUp && evt->Parameter<intptr_t>() != ACTIONS::CURSOR_CLICK )
1021             {
1022                 eatFirstMouseUp = false;
1023                 continue;
1024             }
1025 
1026             break; // finish
1027         }
1028         else if( evt->IsAction( &PCB_ACTIONS::toggle45 ) )
1029         {
1030             lock45 = !lock45;
1031             evt->SetPassEvent( false );
1032         }
1033         else
1034         {
1035             evt->SetPassEvent();
1036         }
1037 
1038     } while( ( evt = Wait() ) ); // Assignment (instead of equality test) is intentional
1039 
1040     controls->ForceCursorPosition( false );
1041     controls->ShowCursor( false );
1042     controls->SetAutoPan( false );
1043 
1044     m_dragging = false;
1045     editFrame->UndoRedoBlock( false );
1046 
1047     if( hasRedrawn3D && restore_state )
1048         editFrame->Update3DView( false, true );
1049 
1050     // Discard reference point when selection is "dropped" onto the board
1051     selection.ClearReferencePoint();
1052 
1053     // TODO: there's an encapsulation leak here: this commit often has more than just the move
1054     // in it; for instance it might have a paste, append board, etc. as well.
1055     if( restore_state )
1056         m_commit->Revert();
1057     else
1058         m_commit->Push( _( "Drag" ) );
1059 
1060     // Remove the dynamic ratsnest from the screen
1061     m_toolMgr->RunAction( PCB_ACTIONS::hideDynamicRatsnest, true );
1062 
1063     // Unselect all items to update flags
1064     m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
1065 
1066     // Reselect items if they were already selected and we completed the move
1067     if( !is_hover && !restore_state )
1068         m_toolMgr->RunAction( PCB_ACTIONS::selectItems, true, &orig_items );
1069 
1070     editFrame->PopTool( tool );
1071 
1072     return restore_state ? -1 : 0;
1073 }
1074 
1075 
ChangeTrackWidth(const TOOL_EVENT & aEvent)1076 int EDIT_TOOL::ChangeTrackWidth( const TOOL_EVENT& aEvent )
1077 {
1078     const PCB_SELECTION& selection = m_selectionTool->RequestSelection(
1079             []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
1080             {
1081                 // Iterate from the back so we don't have to worry about removals.
1082                 for( int i = aCollector.GetCount() - 1; i >= 0; --i )
1083                 {
1084                     BOARD_ITEM* item = aCollector[ i ];
1085 
1086                     if( !dynamic_cast<PCB_TRACK*>( item ) )
1087                         aCollector.Remove( item );
1088                 }
1089             },
1090             true /* prompt user regarding locked items */ );
1091 
1092     for( EDA_ITEM* item : selection )
1093     {
1094         if( item->Type() == PCB_VIA_T )
1095         {
1096             PCB_VIA* via = static_cast<PCB_VIA*>( item );
1097 
1098             m_commit->Modify( via );
1099 
1100             int new_width;
1101             int new_drill;
1102 
1103             if( via->GetViaType() == VIATYPE::MICROVIA )
1104             {
1105                 NETCLASS* netClass = via->GetNetClass();
1106 
1107                 new_width = netClass->GetuViaDiameter();
1108                 new_drill = netClass->GetuViaDrill();
1109             }
1110             else
1111             {
1112                 new_width = board()->GetDesignSettings().GetCurrentViaSize();
1113                 new_drill = board()->GetDesignSettings().GetCurrentViaDrill();
1114             }
1115 
1116             via->SetDrill( new_drill );
1117             via->SetWidth( new_width );
1118         }
1119         else if( item->Type() == PCB_TRACE_T || item->Type() == PCB_ARC_T )
1120         {
1121             PCB_TRACK* track = dynamic_cast<PCB_TRACK*>( item );
1122 
1123             wxCHECK( track, 0 );
1124 
1125             m_commit->Modify( track );
1126 
1127             int new_width = board()->GetDesignSettings().GetCurrentTrackWidth();
1128             track->SetWidth( new_width );
1129         }
1130     }
1131 
1132     m_commit->Push( _("Edit track width/via size") );
1133 
1134     if( selection.IsHover() )
1135     {
1136         m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
1137 
1138         // Notify other tools of the changes -- This updates the visual ratsnest
1139         m_toolMgr->ProcessEvent( EVENTS::SelectedItemsModified );
1140     }
1141 
1142     return 0;
1143 }
1144 
1145 
FilletTracks(const TOOL_EVENT & aEvent)1146 int EDIT_TOOL::FilletTracks( const TOOL_EVENT& aEvent )
1147 {
1148     // Store last used fillet radius to allow pressing "enter" if repeat fillet is required
1149     static long long filletRadiusIU = 0;
1150 
1151     PCB_SELECTION& selection = m_selectionTool->RequestSelection(
1152             []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
1153             {
1154                 // Iterate from the back so we don't have to worry about removals.
1155                 for( int i = aCollector.GetCount() - 1; i >= 0; --i )
1156                 {
1157                     BOARD_ITEM* item = aCollector[i];
1158 
1159                     if( !dynamic_cast<PCB_TRACK*>( item ) )
1160                         aCollector.Remove( item );
1161                 }
1162             },
1163             true /* prompt user regarding locked items */ );
1164 
1165     if( selection.Size() < 2 )
1166     {
1167         frame()->ShowInfoBarMsg( _( "At least two straight track segments must be selected." ) );
1168         return 0;
1169     }
1170 
1171     WX_UNIT_ENTRY_DIALOG dia( frame(), _( "Enter fillet radius:" ), _( "Fillet Tracks" ),
1172                               filletRadiusIU );
1173 
1174     if( dia.ShowModal() == wxID_CANCEL )
1175         return 0;
1176 
1177     filletRadiusIU = dia.GetValue();
1178 
1179     if( filletRadiusIU == 0 )
1180     {
1181         frame()->ShowInfoBarMsg( _( "A radius of zero was entered.\n"
1182                                     "The fillet operation was not performed." ) );
1183         return 0;
1184     }
1185 
1186     struct FILLET_OP
1187     {
1188         PCB_TRACK* t1;
1189         PCB_TRACK* t2;
1190         // Start point of track is modified after PCB_ARC is added, otherwise the end point:
1191         bool       t1Start = true;
1192         bool       t2Start = true;
1193     };
1194 
1195     std::vector<FILLET_OP> filletOperations;
1196     KICAD_T                track_types[] = { PCB_PAD_T, PCB_VIA_T, PCB_TRACE_T, PCB_ARC_T, EOT };
1197     bool                   operationPerformedOnAtLeastOne = false;
1198     bool                   didOneAttemptFail              = false;
1199     std::set<PCB_TRACK*>   processedTracks;
1200 
1201     for( auto it = selection.begin(); it != selection.end(); it++ )
1202     {
1203         PCB_TRACK* track = dyn_cast<PCB_TRACK*>( *it );
1204 
1205         if( !track || track->Type() != PCB_TRACE_T || track->GetLength() == 0 )
1206         {
1207             continue;
1208         }
1209 
1210         auto processFilletOp =
1211                 [&]( bool aStartPoint )
1212                 {
1213                     wxPoint anchor = ( aStartPoint ) ? track->GetStart() : track->GetEnd();
1214                     auto connectivity = board()->GetConnectivity();
1215                     auto itemsOnAnchor = connectivity->GetConnectedItemsAtAnchor( track, anchor,
1216                                                                                   track_types );
1217 
1218                     if( itemsOnAnchor.size() > 0
1219                             && selection.Contains( itemsOnAnchor.at( 0 ) )
1220                             && itemsOnAnchor.at( 0 )->Type() == PCB_TRACE_T )
1221                     {
1222                         PCB_TRACK* trackOther = dyn_cast<PCB_TRACK*>( itemsOnAnchor.at( 0 ) );
1223 
1224                         // Make sure we don't fillet the same pair of tracks twice
1225                         if( processedTracks.find( trackOther ) == processedTracks.end() )
1226                         {
1227                             if( itemsOnAnchor.size() == 1 )
1228                             {
1229                                 FILLET_OP filletOp;
1230                                 filletOp.t1      = track;
1231                                 filletOp.t2      = trackOther;
1232                                 filletOp.t1Start = aStartPoint;
1233                                 filletOp.t2Start = track->IsPointOnEnds( filletOp.t2->GetStart() );
1234                                 filletOperations.push_back( filletOp );
1235                             }
1236                             else
1237                             {
1238                                 // User requested to fillet these two tracks but not possible as
1239                                 // there are other elements connected at that point
1240                                 didOneAttemptFail = true;
1241                             }
1242                         }
1243                     }
1244                 };
1245 
1246         processFilletOp( true ); // on the start point of track
1247         processFilletOp( false ); // on the end point of track
1248 
1249         processedTracks.insert( track );
1250     }
1251 
1252     std::vector<BOARD_ITEM*> itemsToAddToSelection;
1253 
1254     for( FILLET_OP filletOp : filletOperations )
1255     {
1256         PCB_TRACK* track1 = filletOp.t1;
1257         PCB_TRACK* track2 = filletOp.t2;
1258 
1259         bool trackOnStart = track1->IsPointOnEnds( track2->GetStart() );
1260         bool trackOnEnd   = track1->IsPointOnEnds( track2->GetEnd() );
1261 
1262         if( trackOnStart && trackOnEnd )
1263             continue; // Ignore duplicate tracks
1264 
1265         if( ( trackOnStart || trackOnEnd ) && track1->GetLayer() == track2->GetLayer() )
1266         {
1267             SEG t1Seg( track1->GetStart(), track1->GetEnd() );
1268             SEG t2Seg( track2->GetStart(), track2->GetEnd() );
1269 
1270             if( t1Seg.ApproxCollinear( t2Seg ) )
1271                 continue;
1272 
1273             SHAPE_ARC sArc( t1Seg, t2Seg, filletRadiusIU );
1274 
1275             wxPoint t1newPoint, t2newPoint;
1276 
1277             auto setIfPointOnSeg =
1278                     []( wxPoint& aPointToSet, SEG aSegment, VECTOR2I aVecToTest )
1279                     {
1280                         VECTOR2I segToVec = aSegment.NearestPoint( aVecToTest ) - aVecToTest;
1281 
1282                         // Find out if we are on the segment (minimum precision)
1283                         if( segToVec.EuclideanNorm() < SHAPE_ARC::MIN_PRECISION_IU )
1284                         {
1285                             aPointToSet.x = aVecToTest.x;
1286                             aPointToSet.y = aVecToTest.y;
1287                             return true;
1288                         }
1289 
1290                         return false;
1291                     };
1292 
1293             //Do not draw a fillet if the end points of the arc are not within the track segments
1294             if( !setIfPointOnSeg( t1newPoint, t1Seg, sArc.GetP0() )
1295                     && !setIfPointOnSeg( t2newPoint, t2Seg, sArc.GetP0() ) )
1296             {
1297                 didOneAttemptFail = true;
1298                 continue;
1299             }
1300 
1301             if( !setIfPointOnSeg( t1newPoint, t1Seg, sArc.GetP1() )
1302                     && !setIfPointOnSeg( t2newPoint, t2Seg, sArc.GetP1() ) )
1303             {
1304                 didOneAttemptFail = true;
1305                 continue;
1306             }
1307 
1308             PCB_ARC* tArc = new PCB_ARC( frame()->GetBoard(), &sArc );
1309             tArc->SetLayer( track1->GetLayer() );
1310             tArc->SetWidth( track1->GetWidth() );
1311             tArc->SetNet( track1->GetNet() );
1312             tArc->SetLocked( track1->IsLocked() );
1313             m_commit->Add( tArc );
1314             itemsToAddToSelection.push_back( tArc );
1315 
1316             m_commit->Modify( track1 );
1317             m_commit->Modify( track2 );
1318 
1319             if( filletOp.t1Start )
1320                 track1->SetStart( t1newPoint );
1321             else
1322                 track1->SetEnd( t1newPoint );
1323 
1324             if( filletOp.t2Start )
1325                 track2->SetStart( t2newPoint );
1326             else
1327                 track2->SetEnd( t2newPoint );
1328 
1329             operationPerformedOnAtLeastOne = true;
1330         }
1331     }
1332 
1333     m_commit->Push( _( "Fillet Tracks" ) );
1334 
1335     //select the newly created arcs
1336     for( BOARD_ITEM* item : itemsToAddToSelection )
1337         m_selectionTool->AddItemToSel( item );
1338 
1339     if( !operationPerformedOnAtLeastOne )
1340         frame()->ShowInfoBarMsg( _( "Unable to fillet the selected track segments." ) );
1341     else if( didOneAttemptFail )
1342         frame()->ShowInfoBarMsg( _( "Some of the track segments could not be filleted." ) );
1343 
1344     return 0;
1345 }
1346 
1347 
Properties(const TOOL_EVENT & aEvent)1348 int EDIT_TOOL::Properties( const TOOL_EVENT& aEvent )
1349 {
1350     PCB_BASE_EDIT_FRAME* editFrame = getEditFrame<PCB_BASE_EDIT_FRAME>();
1351     const PCB_SELECTION& selection = m_selectionTool->RequestSelection(
1352             []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
1353             {
1354             } );
1355 
1356     // Tracks & vias are treated in a special way:
1357     if( ( SELECTION_CONDITIONS::OnlyTypes( GENERAL_COLLECTOR::Tracks ) )( selection ) )
1358     {
1359             DIALOG_TRACK_VIA_PROPERTIES dlg( editFrame, selection, *m_commit );
1360             dlg.ShowQuasiModal();       // QuasiModal required for NET_SELECTOR
1361     }
1362     else if( selection.Size() == 1 )
1363     {
1364         // Display properties dialog
1365         BOARD_ITEM* item = static_cast<BOARD_ITEM*>( selection.Front() );
1366 
1367         // Do not handle undo buffer, it is done by the properties dialogs
1368         editFrame->OnEditItemRequest( item );
1369 
1370         // Notify other tools of the changes
1371         m_toolMgr->ProcessEvent( EVENTS::SelectedItemsModified );
1372     }
1373     else if( selection.Size() == 0 && getView()->IsLayerVisible( LAYER_DRAWINGSHEET ) )
1374     {
1375         DS_PROXY_VIEW_ITEM* ds = editFrame->GetCanvas()->GetDrawingSheet();
1376         VECTOR2D            cursorPos = getViewControls()->GetCursorPosition( false );
1377 
1378         if( ds && ds->HitTestDrawingSheetItems( getView(), (wxPoint) cursorPos ) )
1379             m_toolMgr->RunAction( ACTIONS::pageSettings );
1380         else
1381             m_toolMgr->RunAction( PCB_ACTIONS::footprintProperties, true );
1382     }
1383 
1384     if( selection.IsHover() )
1385     {
1386         m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
1387     }
1388     else
1389     {
1390         // Check for items becoming invisible and drop them from the selection.
1391 
1392         LSET visible = editFrame->GetBoard()->GetVisibleLayers();
1393 
1394         for( EDA_ITEM* eda_item : selection )
1395         {
1396             BOARD_ITEM* item = static_cast<BOARD_ITEM*>( eda_item );
1397 
1398             if( !( item->GetLayerSet() & visible ).any() )
1399                 m_selectionTool->RemoveItemFromSel( item );
1400         }
1401     }
1402 
1403     return 0;
1404 }
1405 
1406 
Rotate(const TOOL_EVENT & aEvent)1407 int EDIT_TOOL::Rotate( const TOOL_EVENT& aEvent )
1408 {
1409     if( isRouterActive() )
1410     {
1411         wxBell();
1412         return 0;
1413     }
1414 
1415     PCB_BASE_EDIT_FRAME* editFrame = getEditFrame<PCB_BASE_EDIT_FRAME>();
1416 
1417     // Be sure that there is at least one item that we can modify. If nothing was selected before,
1418     // try looking for the stuff under mouse cursor (i.e. KiCad old-style hover selection)
1419     PCB_SELECTION& selection = m_selectionTool->RequestSelection(
1420             []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
1421             {
1422                 sTool->FilterCollectorForMarkers( aCollector );
1423             },
1424             // Prompt user regarding locked items if in board editor and in free-pad-mode (if
1425             // we're not in free-pad mode we delay this until the second RequestSelection()).
1426             frame()->Settings().m_AllowFreePads && !m_isFootprintEditor );
1427 
1428     if( selection.Empty() )
1429         return 0;
1430 
1431     OPT<VECTOR2I> oldRefPt = boost::make_optional<VECTOR2I>( false, VECTOR2I( 0, 0 ) );
1432 
1433     if( selection.HasReferencePoint() )
1434         oldRefPt = selection.GetReferencePoint();
1435 
1436     // Now filter out pads if not in free pads mode.  We cannot do this in the first
1437     // RequestSelection() as we need the reference point when a pad is the selection front.
1438     if( !m_isFootprintEditor && !frame()->Settings().m_AllowFreePads )
1439     {
1440         selection = m_selectionTool->RequestSelection(
1441                 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
1442                 {
1443                     sTool->FilterCollectorForMarkers( aCollector );
1444                     sTool->FilterCollectorForHierarchy( aCollector, true );
1445                     sTool->FilterCollectorForFreePads( aCollector );
1446                 },
1447                 true /* prompt user regarding locked items */ );
1448     }
1449 
1450     updateModificationPoint( selection );
1451 
1452     VECTOR2I refPt = selection.GetReferencePoint();
1453     const int rotateAngle = TOOL_EVT_UTILS::GetEventRotationAngle( *editFrame, aEvent );
1454 
1455     // When editing footprints, all items have the same parent
1456     if( IsFootprintEditor() )
1457         m_commit->Modify( selection.Front() );
1458 
1459     for( EDA_ITEM* item : selection )
1460     {
1461         if( !item->IsNew() && !IsFootprintEditor() )
1462         {
1463             m_commit->Modify( item );
1464 
1465             // If rotating a group, record position of all the descendants for undo
1466             if( item->Type() == PCB_GROUP_T )
1467             {
1468                 static_cast<PCB_GROUP*>( item )->RunOnDescendants(
1469                         [&]( BOARD_ITEM* bItem )
1470                         {
1471                             m_commit->Modify( bItem );
1472                         });
1473             }
1474         }
1475 
1476         static_cast<BOARD_ITEM*>( item )->Rotate( refPt, rotateAngle );
1477     }
1478 
1479     if( !m_dragging )
1480         m_commit->Push( _( "Rotate" ) );
1481 
1482     if( selection.IsHover() && !m_dragging )
1483         m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
1484 
1485     m_toolMgr->ProcessEvent( EVENTS::SelectedItemsModified );
1486 
1487     if( m_dragging )
1488         m_toolMgr->RunAction( PCB_ACTIONS::updateLocalRatsnest, false );
1489 
1490     // Restore the old reference so any mouse dragging that occurs doesn't make the selection jump
1491     // to this now invalid reference
1492     if( oldRefPt )
1493         selection.SetReferencePoint( *oldRefPt );
1494     else
1495         selection.ClearReferencePoint();
1496 
1497     return 0;
1498 }
1499 
1500 
1501 /**
1502  * Mirror a point about the vertical axis passing through another point.
1503  */
mirrorPointX(const wxPoint & aPoint,const wxPoint & aMirrorPoint)1504 static wxPoint mirrorPointX( const wxPoint& aPoint, const wxPoint& aMirrorPoint )
1505 {
1506     wxPoint mirrored = aPoint;
1507 
1508     mirrored.x -= aMirrorPoint.x;
1509     mirrored.x = -mirrored.x;
1510     mirrored.x += aMirrorPoint.x;
1511 
1512     return mirrored;
1513 }
1514 
1515 
1516 /**
1517  * Mirror a pad in the vertical axis passing through a point (mirror left to right).
1518  */
mirrorPadX(PAD & aPad,const wxPoint & aMirrorPoint)1519 static void mirrorPadX( PAD& aPad, const wxPoint& aMirrorPoint )
1520 {
1521     if( aPad.GetShape() == PAD_SHAPE::CUSTOM )
1522         aPad.FlipPrimitives( true );  // mirror primitives left to right
1523 
1524     wxPoint tmpPt = mirrorPointX( aPad.GetPosition(), aMirrorPoint );
1525     aPad.SetPosition( tmpPt );
1526 
1527     aPad.SetX0( aPad.GetPosition().x );
1528 
1529     tmpPt = aPad.GetOffset();
1530     tmpPt.x = -tmpPt.x;
1531     aPad.SetOffset( tmpPt );
1532 
1533     auto tmpz = aPad.GetDelta();
1534     tmpz.x = -tmpz.x;
1535     aPad.SetDelta( tmpz );
1536 
1537     aPad.SetOrientation( -aPad.GetOrientation() );
1538 }
1539 
1540 
Mirror(const TOOL_EVENT & aEvent)1541 int EDIT_TOOL::Mirror( const TOOL_EVENT& aEvent )
1542 {
1543     if( isRouterActive() )
1544     {
1545         wxBell();
1546         return 0;
1547     }
1548 
1549     PCB_SELECTION& selection = m_selectionTool->RequestSelection(
1550             []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
1551             {
1552                 sTool->FilterCollectorForMarkers( aCollector );
1553                 sTool->FilterCollectorForHierarchy( aCollector, true );
1554                 sTool->FilterCollectorForFreePads( aCollector );
1555             },
1556             !m_dragging /* prompt user regarding locked items */ );
1557 
1558     if( selection.Empty() )
1559         return 0;
1560 
1561     updateModificationPoint( selection );
1562     auto refPoint = selection.GetReferencePoint();
1563     wxPoint mirrorPoint( refPoint.x, refPoint.y );
1564 
1565     // When editing footprints, all items have the same parent
1566     if( IsFootprintEditor() )
1567         m_commit->Modify( selection.Front() );
1568 
1569     for( EDA_ITEM* item : selection )
1570     {
1571         // only modify items we can mirror
1572         switch( item->Type() )
1573         {
1574         case PCB_FP_SHAPE_T:
1575         case PCB_FP_TEXT_T:
1576         case PCB_FP_ZONE_T:
1577         case PCB_PAD_T:
1578             // Only create undo entry for items on the board
1579             if( !item->IsNew() && !IsFootprintEditor() )
1580                 m_commit->Modify( item );
1581 
1582             break;
1583         default:
1584             continue;
1585         }
1586 
1587         // modify each object as necessary
1588         switch( item->Type() )
1589         {
1590         case PCB_FP_SHAPE_T:
1591         {
1592             FP_SHAPE* shape = static_cast<FP_SHAPE*>( item );
1593             shape->Mirror( mirrorPoint, false );
1594             break;
1595         }
1596 
1597         case PCB_FP_ZONE_T:
1598         {
1599             FP_ZONE* zone = static_cast<FP_ZONE*>( item );
1600             zone->Mirror( mirrorPoint, false );
1601             break;
1602         }
1603 
1604         case PCB_FP_TEXT_T:
1605         {
1606             FP_TEXT* text = static_cast<FP_TEXT*>( item );
1607             text->Mirror( mirrorPoint, false );
1608             break;
1609         }
1610 
1611         case PCB_PAD_T:
1612         {
1613             PAD* pad = static_cast<PAD*>( item );
1614             mirrorPadX( *pad, mirrorPoint );
1615             break;
1616         }
1617 
1618         default:
1619             // it's likely the commit object is wrong if you get here
1620             // Unsure if PCB_GROUP_T needs special attention here.
1621             assert( false );
1622             break;
1623         }
1624     }
1625 
1626     if( !m_dragging )
1627         m_commit->Push( _( "Mirror" ) );
1628 
1629     if( selection.IsHover() && !m_dragging )
1630         m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
1631 
1632     m_toolMgr->ProcessEvent( EVENTS::SelectedItemsModified );
1633 
1634     if( m_dragging )
1635         m_toolMgr->RunAction( PCB_ACTIONS::updateLocalRatsnest, false );
1636 
1637     return 0;
1638 }
1639 
1640 
Flip(const TOOL_EVENT & aEvent)1641 int EDIT_TOOL::Flip( const TOOL_EVENT& aEvent )
1642 {
1643     if( isRouterActive() )
1644     {
1645         wxBell();
1646         return 0;
1647     }
1648 
1649     PCB_SELECTION& selection = m_selectionTool->RequestSelection(
1650             []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
1651             {
1652                 sTool->FilterCollectorForMarkers( aCollector );
1653                 sTool->FilterCollectorForHierarchy( aCollector, true );
1654                 sTool->FilterCollectorForFreePads( aCollector );
1655             },
1656             !m_dragging /* prompt user regarding locked items */ );
1657 
1658     if( selection.Empty() )
1659         return 0;
1660 
1661     OPT<VECTOR2I> oldRefPt = boost::make_optional<VECTOR2I>( false, VECTOR2I( 0, 0 ) );
1662 
1663     if( selection.HasReferencePoint() )
1664         oldRefPt = selection.GetReferencePoint();
1665 
1666     updateModificationPoint( selection );
1667 
1668     // Flip around the anchor for footprints, and the bounding box center for board items
1669     VECTOR2I refPt = IsFootprintEditor() ? VECTOR2I( 0, 0 ) : selection.GetCenter();
1670 
1671     // If only one item selected, flip around the selection or item anchor point (instead
1672     // of the bounding box center) to avoid moving the item anchor
1673     if( selection.GetSize() == 1 )
1674     {
1675         if( m_dragging && selection.HasReferencePoint() )
1676             refPt = selection.GetReferencePoint();
1677         else
1678             refPt = static_cast<BOARD_ITEM*>( selection.GetItem( 0 ) )->GetPosition();
1679     }
1680 
1681     bool leftRight = frame()->Settings().m_FlipLeftRight;
1682 
1683     // When editing footprints, all items have the same parent
1684     if( IsFootprintEditor() )
1685         m_commit->Modify( selection.Front() );
1686 
1687     for( EDA_ITEM* item : selection )
1688     {
1689         if( !item->IsNew() && !IsFootprintEditor() )
1690             m_commit->Modify( item );
1691 
1692         if( item->Type() == PCB_GROUP_T )
1693         {
1694             static_cast<PCB_GROUP*>( item )->RunOnDescendants( [&]( BOARD_ITEM* bItem )
1695                                                                {
1696                                                                    m_commit->Modify( bItem );
1697                                                                });
1698         }
1699 
1700         static_cast<BOARD_ITEM*>( item )->Flip( refPt, leftRight );
1701     }
1702 
1703     if( !m_dragging )
1704         m_commit->Push( _( "Change Side / Flip" ) );
1705 
1706     if( selection.IsHover() && !m_dragging )
1707         m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
1708 
1709     m_toolMgr->ProcessEvent( EVENTS::SelectedItemsModified );
1710 
1711     if( m_dragging )
1712         m_toolMgr->RunAction( PCB_ACTIONS::updateLocalRatsnest, false );
1713 
1714     // Restore the old reference so any mouse dragging that occurs doesn't make the selection jump
1715     // to this now invalid reference
1716     if( oldRefPt )
1717         selection.SetReferencePoint( *oldRefPt );
1718     else
1719         selection.ClearReferencePoint();
1720 
1721     return 0;
1722 }
1723 
1724 
Remove(const TOOL_EVENT & aEvent)1725 int EDIT_TOOL::Remove( const TOOL_EVENT& aEvent )
1726 {
1727     if( isRouterActive() )
1728     {
1729         m_toolMgr->RunAction( PCB_ACTIONS::routerUndoLastSegment, true );
1730         return 0;
1731     }
1732 
1733     std::vector<BOARD_ITEM*> lockedItems;
1734     Activate();
1735 
1736     // get a copy instead of reference (as we're going to clear the selection before removing items)
1737     PCB_SELECTION selectionCopy;
1738     bool isCut = aEvent.Parameter<PCB_ACTIONS::REMOVE_FLAGS>() == PCB_ACTIONS::REMOVE_FLAGS::CUT;
1739     bool isAlt = aEvent.Parameter<PCB_ACTIONS::REMOVE_FLAGS>() == PCB_ACTIONS::REMOVE_FLAGS::ALT;
1740 
1741     // If we are in a "Cut" operation, then the copied selection exists already and we want to
1742     // delete exactly that; no more, no fewer.  Any filtering for locked items must be done in
1743     // the copyToClipboard() routine.
1744     if( isCut )
1745     {
1746         selectionCopy = m_selectionTool->GetSelection();
1747     }
1748     else
1749     {
1750         // When not in free-pad mode we normally auto-promote selected pads to their parent
1751         // footprints.  But this is probably a little too dangerous for a destructive operation,
1752         // so we just do the promotion but not the deletion (allowing for a second delete to do
1753         // it if that's what the user wanted).
1754         selectionCopy = m_selectionTool->RequestSelection(
1755                 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
1756                 {
1757                 } );
1758 
1759         size_t beforeFPCount = selectionCopy.CountType( PCB_FOOTPRINT_T );
1760 
1761         m_selectionTool->RequestSelection(
1762                 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
1763                 {
1764                     sTool->FilterCollectorForFreePads( aCollector );
1765                 } );
1766 
1767         if( !selectionCopy.IsHover()
1768                 && m_selectionTool->GetSelection().CountType( PCB_FOOTPRINT_T ) > beforeFPCount )
1769         {
1770             wxBell();
1771             canvas()->Refresh();
1772             return 0;
1773         }
1774 
1775         // In "alternative" mode, we expand selected track items to their full connection.
1776         if( isAlt && ( selectionCopy.HasType( PCB_TRACE_T ) || selectionCopy.HasType( PCB_VIA_T ) ) )
1777         {
1778             m_toolMgr->RunAction( PCB_ACTIONS::selectConnection, true );
1779         }
1780 
1781         // Finally run RequestSelection() one more time to find out what user wants to do about
1782         // locked objects.
1783         selectionCopy = m_selectionTool->RequestSelection(
1784                 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
1785                 {
1786                     sTool->FilterCollectorForFreePads( aCollector );
1787                 },
1788                 true /* prompt user regarding locked items */ );
1789     }
1790 
1791     // As we are about to remove items, they have to be removed from the selection first
1792     m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
1793 
1794     for( EDA_ITEM* item : selectionCopy )
1795     {
1796         PCB_GROUP* parentGroup = static_cast<BOARD_ITEM*>( item )->GetParentGroup();
1797 
1798         if( parentGroup )
1799         {
1800             m_commit->Modify( parentGroup );
1801             parentGroup->RemoveItem( static_cast<BOARD_ITEM*>( item ) );
1802         }
1803 
1804         switch( item->Type() )
1805         {
1806         case PCB_FP_TEXT_T:
1807         {
1808             FP_TEXT*   text = static_cast<FP_TEXT*>( item );
1809             FOOTPRINT* parent = static_cast<FOOTPRINT*>( item->GetParent() );
1810 
1811             if( text->GetType() == FP_TEXT::TEXT_is_DIVERS )
1812             {
1813                 m_commit->Modify( parent );
1814                 getView()->Remove( text );
1815                 parent->Remove( text );
1816             }
1817 
1818             break;
1819         }
1820 
1821         case PCB_PAD_T:
1822             if( IsFootprintEditor() || frame()->Settings().m_AllowFreePads )
1823             {
1824                 PAD*       pad = static_cast<PAD*>( item );
1825                 FOOTPRINT* parent = static_cast<FOOTPRINT*>( item->GetParent() );
1826 
1827                 m_commit->Modify( parent );
1828                 getView()->Remove( pad );
1829                 parent->Remove( pad );
1830             }
1831 
1832             break;
1833 
1834         case PCB_FP_ZONE_T:
1835         {
1836             FP_ZONE*   zone = static_cast<FP_ZONE*>( item );
1837             FOOTPRINT* parent = static_cast<FOOTPRINT*>( item->GetParent() );
1838 
1839             m_commit->Modify( parent );
1840             getView()->Remove( zone );
1841             parent->Remove( zone );
1842             break;
1843         }
1844 
1845         case PCB_ZONE_T:
1846         // We process the zones special so that cutouts can be deleted when the delete tool
1847         // is called from inside a cutout when the zone is selected.
1848         {
1849             // Only interact with cutouts when deleting and a single item is selected
1850             if( !isCut && selectionCopy.GetSize() == 1 )
1851             {
1852                 VECTOR2I curPos = getViewControls()->GetCursorPosition();
1853                 ZONE*    zone   = static_cast<ZONE*>( item );
1854 
1855                 int outlineIdx, holeIdx;
1856 
1857                 if( zone->HitTestCutout( curPos, &outlineIdx, &holeIdx ) )
1858                 {
1859                     // Remove the cutout
1860                     m_commit->Modify( zone );
1861                     zone->RemoveCutout( outlineIdx, holeIdx );
1862                     zone->UnFill();
1863 
1864                     // TODO Refill zone when KiCad supports auto re-fill
1865 
1866                     // Update the display
1867                     zone->HatchBorder();
1868                     canvas()->Refresh();
1869 
1870                     // Restore the selection on the original zone
1871                     m_toolMgr->RunAction( PCB_ACTIONS::selectItem, true, zone );
1872 
1873                     break;
1874                 }
1875             }
1876 
1877             // Remove the entire zone otherwise
1878             m_commit->Remove( item );
1879             break;
1880         }
1881 
1882         case PCB_GROUP_T:
1883         {
1884             PCB_GROUP* group = static_cast<PCB_GROUP*>( item );
1885 
1886             auto removeItem =
1887                     [&]( BOARD_ITEM* bItem )
1888                     {
1889                         if( bItem->GetParent() && bItem->GetParent()->Type() == PCB_FOOTPRINT_T )
1890                         {
1891                             // Silently ignore delete of Reference or Value if they happen to be
1892                             // in group.
1893                             if( bItem->Type() == PCB_FP_TEXT_T )
1894                             {
1895                                 FP_TEXT* textItem = static_cast<FP_TEXT*>( bItem );
1896 
1897                                 if( textItem->GetType() != FP_TEXT::TEXT_is_DIVERS )
1898                                     return;
1899                             }
1900                             else if( bItem->Type() == PCB_PAD_T )
1901                             {
1902                                 if( !IsFootprintEditor() && !frame()->Settings().m_AllowFreePads )
1903                                     return;
1904                             }
1905 
1906                             m_commit->Modify( bItem->GetParent() );
1907                             getView()->Remove( bItem );
1908                             bItem->GetParent()->Remove( bItem );
1909                         }
1910                         else
1911                         {
1912                             m_commit->Remove( bItem );
1913                         }
1914                     };
1915 
1916             removeItem( group );
1917 
1918             group->RunOnDescendants( [&]( BOARD_ITEM* aDescendant )
1919                                      {
1920                                          removeItem( aDescendant );
1921                                      });
1922             break;
1923         }
1924 
1925         default:
1926             m_commit->Remove( item );
1927             break;
1928         }
1929     }
1930 
1931     // If the entered group has been emptied then leave it.
1932     PCB_GROUP* enteredGroup = m_selectionTool->GetEnteredGroup();
1933 
1934     if( enteredGroup && enteredGroup->GetItems().empty() )
1935         m_selectionTool->ExitGroup();
1936 
1937     if( isCut )
1938         m_commit->Push( _( "Cut" ) );
1939     else
1940         m_commit->Push( _( "Delete" ) );
1941 
1942     return 0;
1943 }
1944 
1945 
MoveExact(const TOOL_EVENT & aEvent)1946 int EDIT_TOOL::MoveExact( const TOOL_EVENT& aEvent )
1947 {
1948     if( isRouterActive() )
1949     {
1950         wxBell();
1951         return 0;
1952     }
1953 
1954     const PCB_SELECTION& selection = m_selectionTool->RequestSelection(
1955             []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
1956             {
1957                 sTool->FilterCollectorForMarkers( aCollector );
1958                 sTool->FilterCollectorForHierarchy( aCollector, true );
1959             },
1960             true /* prompt user regarding locked items */ );
1961 
1962     if( selection.Empty() )
1963         return 0;
1964 
1965     wxPoint         translation;
1966     double          rotation;
1967     ROTATION_ANCHOR rotationAnchor = selection.Size() > 1 ? ROTATE_AROUND_SEL_CENTER
1968                                                           : ROTATE_AROUND_ITEM_ANCHOR;
1969 
1970     // TODO: Implement a visible bounding border at the edge
1971     auto sel_box = selection.GetBoundingBox();
1972 
1973     DIALOG_MOVE_EXACT dialog( frame(), translation, rotation, rotationAnchor, sel_box );
1974     int ret = dialog.ShowModal();
1975 
1976     if( ret == wxID_OK )
1977     {
1978         VECTOR2I rp = selection.GetCenter();
1979         wxPoint selCenter( rp.x, rp.y );
1980 
1981         // Make sure the rotation is from the right reference point
1982         selCenter += translation;
1983 
1984         if( !frame()->GetDisplayOptions().m_DisplayInvertYAxis )
1985             rotation *= -1.0;
1986 
1987         // When editing footprints, all items have the same parent
1988         if( IsFootprintEditor() )
1989             m_commit->Modify( selection.Front() );
1990 
1991         for( EDA_ITEM* selItem : selection )
1992         {
1993             BOARD_ITEM* item = static_cast<BOARD_ITEM*>( selItem );
1994 
1995             if( !item->IsNew() && !IsFootprintEditor() )
1996             {
1997                 m_commit->Modify( item );
1998 
1999                 if( item->Type() == PCB_GROUP_T )
2000                 {
2001                     PCB_GROUP* group = static_cast<PCB_GROUP*>( item );
2002 
2003                     group->RunOnDescendants( [&]( BOARD_ITEM* bItem )
2004                                              {
2005                                                  m_commit->Modify( bItem );
2006                                              });
2007                 }
2008             }
2009 
2010             if( !item->GetParent() || !item->GetParent()->IsSelected() )
2011                 item->Move( translation );
2012 
2013             switch( rotationAnchor )
2014             {
2015             case ROTATE_AROUND_ITEM_ANCHOR:
2016                 item->Rotate( item->GetPosition(), rotation );
2017                 break;
2018             case ROTATE_AROUND_SEL_CENTER:
2019                 item->Rotate( selCenter, rotation );
2020                 break;
2021             case ROTATE_AROUND_USER_ORIGIN:
2022                 item->Rotate( (wxPoint) frame()->GetScreen()->m_LocalOrigin, rotation );
2023                 break;
2024             case ROTATE_AROUND_AUX_ORIGIN:
2025                 item->Rotate( board()->GetDesignSettings().GetAuxOrigin(), rotation );
2026                 break;
2027             }
2028 
2029             if( !m_dragging )
2030                 getView()->Update( item );
2031         }
2032 
2033         m_commit->Push( _( "Move exact" ) );
2034 
2035         if( selection.IsHover() )
2036             m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
2037 
2038         m_toolMgr->ProcessEvent( EVENTS::SelectedItemsModified );
2039 
2040         if( m_dragging )
2041             m_toolMgr->RunAction( PCB_ACTIONS::updateLocalRatsnest, false );
2042     }
2043 
2044     return 0;
2045 }
2046 
2047 
Duplicate(const TOOL_EVENT & aEvent)2048 int EDIT_TOOL::Duplicate( const TOOL_EVENT& aEvent )
2049 {
2050     if( isRouterActive() )
2051     {
2052         wxBell();
2053         return 0;
2054     }
2055 
2056     bool increment = aEvent.IsAction( &PCB_ACTIONS::duplicateIncrement );
2057 
2058     // Be sure that there is at least one item that we can modify
2059     const PCB_SELECTION& selection = m_selectionTool->RequestSelection(
2060                 []( const VECTOR2I&, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
2061                 {
2062                     sTool->FilterCollectorForMarkers( aCollector );
2063                     sTool->FilterCollectorForHierarchy( aCollector, true );
2064                 } );
2065 
2066     if( selection.Empty() )
2067         return 0;
2068 
2069     // we have a selection to work on now, so start the tool process
2070     PCB_BASE_EDIT_FRAME* editFrame = getEditFrame<PCB_BASE_EDIT_FRAME>();
2071 
2072     // If the selection was given a hover, we do not keep the selection after completion
2073     bool is_hover = selection.IsHover();
2074 
2075     std::vector<BOARD_ITEM*> new_items;
2076     new_items.reserve( selection.Size() );
2077 
2078     // Each selected item is duplicated and pushed to new_items list
2079     // Old selection is cleared, and new items are then selected.
2080     for( EDA_ITEM* item : selection )
2081     {
2082         BOARD_ITEM* dupe_item = nullptr;
2083         BOARD_ITEM* orig_item = static_cast<BOARD_ITEM*>( item );
2084 
2085         if( m_isFootprintEditor )
2086         {
2087             FOOTPRINT* parentFootprint = editFrame->GetBoard()->GetFirstFootprint();
2088             dupe_item = parentFootprint->DuplicateItem( orig_item );
2089 
2090             if( increment && dupe_item->Type() == PCB_PAD_T
2091                 && static_cast<PAD*>( dupe_item )->CanHaveNumber() )
2092             {
2093                 PAD_TOOL* padTool = m_toolMgr->GetTool<PAD_TOOL>();
2094                 wxString padNumber = padTool->GetLastPadNumber();
2095                 padNumber = parentFootprint->GetNextPadNumber( padNumber );
2096                 padTool->SetLastPadNumber( padNumber );
2097                 static_cast<PAD*>( dupe_item )->SetNumber( padNumber );
2098             }
2099         }
2100         else if( orig_item->GetParent() && orig_item->GetParent()->Type() == PCB_FOOTPRINT_T )
2101         {
2102             FOOTPRINT* parentFootprint = static_cast<FOOTPRINT*>( orig_item->GetParent() );
2103 
2104             m_commit->Modify( parentFootprint );
2105             dupe_item = parentFootprint->DuplicateItem( orig_item, true /* add to parent */ );
2106         }
2107         else
2108         {
2109             switch( orig_item->Type() )
2110             {
2111             case PCB_FOOTPRINT_T:
2112             case PCB_TEXT_T:
2113             case PCB_SHAPE_T:
2114             case PCB_TRACE_T:
2115             case PCB_ARC_T:
2116             case PCB_VIA_T:
2117             case PCB_ZONE_T:
2118             case PCB_TARGET_T:
2119             case PCB_DIM_ALIGNED_T:
2120             case PCB_DIM_CENTER_T:
2121             case PCB_DIM_ORTHOGONAL_T:
2122             case PCB_DIM_LEADER_T:
2123                 dupe_item = orig_item->Duplicate();
2124                 break;
2125 
2126             case PCB_GROUP_T:
2127                 dupe_item = static_cast<PCB_GROUP*>( orig_item )->DeepDuplicate();
2128                 break;
2129 
2130             default:
2131                 wxASSERT_MSG( false, wxString::Format( "Unhandled item type %d",
2132                                                        orig_item->Type() ) );
2133                 break;
2134             }
2135         }
2136 
2137         if( dupe_item )
2138         {
2139             if( dupe_item->Type() == PCB_GROUP_T )
2140             {
2141                 static_cast<PCB_GROUP*>( dupe_item )->RunOnDescendants(
2142                         [&]( BOARD_ITEM* bItem )
2143                         {
2144                             m_commit->Add( bItem );
2145                         });
2146             }
2147 
2148             // Clear the selection flag here, otherwise the PCB_SELECTION_TOOL
2149             // will not properly select it later on
2150             dupe_item->ClearSelected();
2151 
2152             new_items.push_back( dupe_item );
2153             m_commit->Add( dupe_item );
2154         }
2155     }
2156 
2157     // Clear the old selection first
2158     m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
2159 
2160     // Select the new items
2161     m_toolMgr->RunAction( PCB_ACTIONS::selectItems, true, &new_items );
2162 
2163     // record the new items as added
2164     if( !selection.Empty() )
2165     {
2166         editFrame->DisplayToolMsg( wxString::Format( _( "Duplicated %d item(s)" ),
2167                                                      (int) new_items.size() ) );
2168 
2169         // TODO(ISM): This line can't be used to activate the tool until we allow multiple
2170         //            activations.
2171         // m_toolMgr->RunAction( PCB_ACTIONS::move, true );
2172         // Instead we have to create the event and call the tool's function
2173         // directly
2174 
2175         // If items were duplicated, pick them up
2176         // this works well for "dropping" copies around and pushes the commit
2177         TOOL_EVENT evt = PCB_ACTIONS::move.MakeEvent();
2178         Move( evt );
2179 
2180         // Deslect the duplicated item if we originally started as a hover selection
2181         if( is_hover )
2182             m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
2183     }
2184 
2185     return 0;
2186 }
2187 
2188 
CreateArray(const TOOL_EVENT & aEvent)2189 int EDIT_TOOL::CreateArray( const TOOL_EVENT& aEvent )
2190 {
2191     if( isRouterActive() )
2192     {
2193         wxBell();
2194         return 0;
2195     }
2196 
2197     // Be sure that there is at least one item that we can modify
2198     const auto& selection = m_selectionTool->RequestSelection(
2199                 []( const VECTOR2I&, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
2200                 {
2201                     sTool->FilterCollectorForMarkers( aCollector );
2202                     sTool->FilterCollectorForHierarchy( aCollector, true );
2203                 } );
2204 
2205     if( selection.Empty() )
2206         return 0;
2207 
2208     // we have a selection to work on now, so start the tool process
2209     PCB_BASE_FRAME* editFrame = getEditFrame<PCB_BASE_FRAME>();
2210     ARRAY_CREATOR   array_creator( *editFrame, m_isFootprintEditor, selection, m_toolMgr );
2211     array_creator.Invoke();
2212 
2213     return 0;
2214 }
2215 
2216 
PadFilter(const VECTOR2I &,GENERAL_COLLECTOR & aCollector,PCB_SELECTION_TOOL * sTool)2217 void EDIT_TOOL::PadFilter( const VECTOR2I&, GENERAL_COLLECTOR& aCollector,
2218                            PCB_SELECTION_TOOL* sTool )
2219 {
2220     for( int i = aCollector.GetCount() - 1; i >= 0; i-- )
2221     {
2222         BOARD_ITEM* item = static_cast<BOARD_ITEM*>( aCollector[i] );
2223 
2224         if( item->Type() != PCB_PAD_T )
2225             aCollector.Remove( i );
2226     }
2227 }
2228 
2229 
FootprintFilter(const VECTOR2I &,GENERAL_COLLECTOR & aCollector,PCB_SELECTION_TOOL * sTool)2230 void EDIT_TOOL::FootprintFilter( const VECTOR2I&, GENERAL_COLLECTOR& aCollector,
2231                                  PCB_SELECTION_TOOL* sTool )
2232 {
2233     for( int i = aCollector.GetCount() - 1; i >= 0; i-- )
2234     {
2235         BOARD_ITEM* item = static_cast<BOARD_ITEM*>( aCollector[i] );
2236 
2237         if( item->Type() != PCB_FOOTPRINT_T )
2238             aCollector.Remove( i );
2239     }
2240 }
2241 
2242 
updateModificationPoint(PCB_SELECTION & aSelection)2243 bool EDIT_TOOL::updateModificationPoint( PCB_SELECTION& aSelection )
2244 {
2245     if( m_dragging && aSelection.HasReferencePoint() )
2246         return false;
2247 
2248     // When there is only one item selected, the reference point is its position...
2249     if( aSelection.Size() == 1 )
2250     {
2251         auto item =  static_cast<BOARD_ITEM*>( aSelection.Front() );
2252         auto pos = item->GetPosition();
2253         aSelection.SetReferencePoint( VECTOR2I( pos.x, pos.y ) );
2254     }
2255     // ...otherwise modify items with regard to the grid-snapped center position
2256     else
2257     {
2258         PCB_GRID_HELPER grid( m_toolMgr, frame()->GetMagneticItemsSettings() );
2259         aSelection.SetReferencePoint( grid.BestSnapAnchor( aSelection.GetCenter(), nullptr ) );
2260     }
2261 
2262     return true;
2263 }
2264 
2265 
pickReferencePoint(const wxString & aTooltip,const wxString & aSuccessMessage,const wxString & aCanceledMessage,VECTOR2I & aReferencePoint)2266 bool EDIT_TOOL::pickReferencePoint( const wxString& aTooltip, const wxString& aSuccessMessage,
2267                                     const wxString& aCanceledMessage, VECTOR2I& aReferencePoint )
2268 {
2269     PCB_PICKER_TOOL* picker = m_toolMgr->GetTool<PCB_PICKER_TOOL>();
2270     OPT<VECTOR2I>    pickedPoint;
2271     bool             done = false;
2272 
2273     m_statusPopup->SetText( aTooltip );
2274 
2275     /// This allow the option of snapping in the tool
2276     picker->SetSnapping( true );
2277 
2278     picker->SetClickHandler(
2279             [&]( const VECTOR2D& aPoint ) -> bool
2280             {
2281                 pickedPoint = aPoint;
2282 
2283                 if( !aSuccessMessage.empty() )
2284                 {
2285                     m_statusPopup->SetText( aSuccessMessage );
2286                     m_statusPopup->Expire( 800 );
2287                 }
2288                 else
2289                 {
2290                     m_statusPopup->Hide();
2291                 }
2292 
2293                 return false; // we don't need any more points
2294             } );
2295 
2296     picker->SetMotionHandler(
2297             [&]( const VECTOR2D& aPos )
2298             {
2299                 m_statusPopup->Move( wxGetMousePosition() + wxPoint( 20, -50 ) );
2300             } );
2301 
2302     picker->SetCancelHandler(
2303             [&]()
2304             {
2305                 if( !aCanceledMessage.empty() )
2306                 {
2307                     m_statusPopup->SetText( aCanceledMessage );
2308                     m_statusPopup->Expire( 800 );
2309                 }
2310                 else
2311                 {
2312                     m_statusPopup->Hide();
2313                 }
2314             } );
2315 
2316     picker->SetFinalizeHandler(
2317             [&]( const int& aFinalState )
2318             {
2319                 done = true;
2320             } );
2321 
2322     m_statusPopup->Move( wxGetMousePosition() + wxPoint( 20, -50 ) );
2323     m_statusPopup->Popup();
2324 
2325     std::string tool = "";
2326     m_toolMgr->RunAction( ACTIONS::pickerTool, true, &tool );
2327 
2328     while( !done )
2329     {
2330         // Pass events unless we receive a null event, then we must shut down
2331         if( TOOL_EVENT* evt = Wait() )
2332             evt->SetPassEvent();
2333         else
2334             break;
2335     }
2336 
2337     // Ensure statusPopup is hidden after use and before deleting it:
2338     m_statusPopup->Hide();
2339 
2340     if( pickedPoint.is_initialized() )
2341         aReferencePoint = pickedPoint.get();
2342 
2343     return pickedPoint.is_initialized();
2344 }
2345 
2346 
copyToClipboard(const TOOL_EVENT & aEvent)2347 int EDIT_TOOL::copyToClipboard( const TOOL_EVENT& aEvent )
2348 {
2349     std::string  tool = "pcbnew.InteractiveEdit.selectReferencePoint";
2350     CLIPBOARD_IO io;
2351     PCB_GRID_HELPER grid( m_toolMgr,
2352                           getEditFrame<PCB_BASE_EDIT_FRAME>()->GetMagneticItemsSettings() );
2353 
2354     frame()->PushTool( tool );
2355     Activate();
2356 
2357     PCB_SELECTION& selection = m_selectionTool->RequestSelection(
2358             []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
2359             {
2360                 for( int i = aCollector.GetCount() - 1; i >= 0; --i )
2361                 {
2362                     BOARD_ITEM* item = aCollector[i];
2363 
2364                     // We can't copy both a footprint and its text in the same operation, so if
2365                     // both are selected, remove the text
2366                     if( item->Type() == PCB_FP_TEXT_T && aCollector.HasItem( item->GetParent() ) )
2367                         aCollector.Remove( item );
2368                 }
2369             },
2370 
2371             // Prompt user regarding locked items.
2372             aEvent.IsAction( &ACTIONS::cut ) && !m_isFootprintEditor );
2373 
2374     if( !selection.Empty() )
2375     {
2376         std::vector<BOARD_ITEM*> items;
2377 
2378         for( EDA_ITEM* item : selection )
2379             items.push_back( static_cast<BOARD_ITEM*>( item ) );
2380 
2381         VECTOR2I refPoint;
2382 
2383         if( aEvent.IsAction( &PCB_ACTIONS::copyWithReference ) )
2384         {
2385             if( !pickReferencePoint( _( "Select reference point for the copy..." ),
2386                                      _( "Selection copied" ),
2387                                      _( "Copy canceled" ),
2388                                      refPoint ) )
2389             {
2390                 frame()->PopTool( tool );
2391                 return 0;
2392             }
2393         }
2394         else
2395         {
2396             refPoint = grid.BestDragOrigin( getViewControls()->GetCursorPosition(), items );
2397         }
2398 
2399         selection.SetReferencePoint( refPoint );
2400 
2401         io.SetBoard( board() );
2402         io.SaveSelection( selection, m_isFootprintEditor );
2403         frame()->SetStatusText( _( "Selection copied" ) );
2404     }
2405 
2406     frame()->PopTool( tool );
2407 
2408     return 0;
2409 }
2410 
2411 
cutToClipboard(const TOOL_EVENT & aEvent)2412 int EDIT_TOOL::cutToClipboard( const TOOL_EVENT& aEvent )
2413 {
2414     if( !copyToClipboard( aEvent ) )
2415     {
2416         // N.B. Setting the CUT flag prevents lock filtering as we only want to delete the items
2417         // that were copied to the clipboard, no more, no fewer.  Filtering for locked item, if
2418         // any will be done in the copyToClipboard() routine
2419         TOOL_EVENT evt( aEvent.Category(), aEvent.Action(), TOOL_ACTION_SCOPE::AS_GLOBAL );
2420         evt.SetParameter( PCB_ACTIONS::REMOVE_FLAGS::CUT );
2421         Remove( evt );
2422     }
2423 
2424     return 0;
2425 }
2426 
2427 
setTransitions()2428 void EDIT_TOOL::setTransitions()
2429 {
2430     Go( &EDIT_TOOL::GetAndPlace,         PCB_ACTIONS::getAndPlace.MakeEvent() );
2431     Go( &EDIT_TOOL::Move,                PCB_ACTIONS::move.MakeEvent() );
2432     Go( &EDIT_TOOL::Drag,                PCB_ACTIONS::drag45Degree.MakeEvent() );
2433     Go( &EDIT_TOOL::Drag,                PCB_ACTIONS::dragFreeAngle.MakeEvent() );
2434     Go( &EDIT_TOOL::Rotate,              PCB_ACTIONS::rotateCw.MakeEvent() );
2435     Go( &EDIT_TOOL::Rotate,              PCB_ACTIONS::rotateCcw.MakeEvent() );
2436     Go( &EDIT_TOOL::Flip,                PCB_ACTIONS::flip.MakeEvent() );
2437     Go( &EDIT_TOOL::Remove,              ACTIONS::doDelete.MakeEvent() );
2438     Go( &EDIT_TOOL::Remove,              PCB_ACTIONS::deleteFull.MakeEvent() );
2439     Go( &EDIT_TOOL::Properties,          PCB_ACTIONS::properties.MakeEvent() );
2440     Go( &EDIT_TOOL::MoveExact,           PCB_ACTIONS::moveExact.MakeEvent() );
2441     Go( &EDIT_TOOL::MoveWithReference,   PCB_ACTIONS::moveWithReference.MakeEvent() );
2442     Go( &EDIT_TOOL::Duplicate,           ACTIONS::duplicate.MakeEvent() );
2443     Go( &EDIT_TOOL::Duplicate,           PCB_ACTIONS::duplicateIncrement.MakeEvent() );
2444     Go( &EDIT_TOOL::CreateArray,         PCB_ACTIONS::createArray.MakeEvent() );
2445     Go( &EDIT_TOOL::Mirror,              PCB_ACTIONS::mirror.MakeEvent() );
2446     Go( &EDIT_TOOL::ChangeTrackWidth,    PCB_ACTIONS::changeTrackWidth.MakeEvent() );
2447     Go( &EDIT_TOOL::FilletTracks,        PCB_ACTIONS::filletTracks.MakeEvent() );
2448 
2449     Go( &EDIT_TOOL::copyToClipboard,     ACTIONS::copy.MakeEvent() );
2450     Go( &EDIT_TOOL::copyToClipboard,     PCB_ACTIONS::copyWithReference.MakeEvent() );
2451     Go( &EDIT_TOOL::cutToClipboard,      ACTIONS::cut.MakeEvent() );
2452 }
2453