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) 2018-2021 KiCad Developers, see AUTHORS.txt for contributors.
6  * @author Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
7  * @author Maciej Suminski <maciej.suminski@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 <limits>
28 #include <functional>
29 using namespace std::placeholders;
30 #include <core/kicad_algo.h>
31 #include <board.h>
32 #include <board_design_settings.h>
33 #include <board_item.h>
34 #include <clipper.hpp>
35 #include <pcb_track.h>
36 #include <footprint.h>
37 #include <pad.h>
38 #include <pcb_group.h>
39 #include <pcb_shape.h>
40 #include <pcb_text.h>
41 #include <pcb_marker.h>
42 #include <zone.h>
43 #include <collectors.h>
44 #include <dialog_filter_selection.h>
45 #include <dialogs/dialog_locked_items_query.h>
46 #include <class_draw_panel_gal.h>
47 #include <view/view_controls.h>
48 #include <preview_items/selection_area.h>
49 #include <painter.h>
50 #include <router/router_tool.h>
51 #include <bitmaps.h>
52 #include <pcbnew_settings.h>
53 #include <tool/tool_event.h>
54 #include <tool/tool_manager.h>
55 #include <tools/tool_event_utils.h>
56 #include <tools/pcb_point_editor.h>
57 #include <tools/pcb_selection_tool.h>
58 #include <tools/pcb_actions.h>
59 #include <connectivity/connectivity_data.h>
60 #include <footprint_viewer_frame.h>
61 #include <id.h>
62 #include <wx/event.h>
63 #include <wx/timer.h>
64 #include <wx/log.h>
65 
66 
67 class SELECT_MENU : public ACTION_MENU
68 {
69 public:
SELECT_MENU()70     SELECT_MENU() :
71         ACTION_MENU( true )
72     {
73         SetTitle( _( "Select" ) );
74 
75         Add( PCB_ACTIONS::filterSelection );
76 
77         AppendSeparator();
78 
79         Add( PCB_ACTIONS::selectConnection );
80         Add( PCB_ACTIONS::selectNet );
81 
82         // This could be enabled if we have better logic for picking the target net with the mouse
83         // Add( PCB_ACTIONS::deselectNet );
84         Add( PCB_ACTIONS::selectSameSheet );
85     }
86 
87 private:
create() const88     ACTION_MENU* create() const override
89     {
90         return new SELECT_MENU();
91     }
92 };
93 
94 
95 /**
96  * Private implementation of firewalled private data.
97  */
98 class PCB_SELECTION_TOOL::PRIV
99 {
100 public:
101     DIALOG_FILTER_SELECTION::OPTIONS m_filterOpts;
102 };
103 
104 
PCB_SELECTION_TOOL()105 PCB_SELECTION_TOOL::PCB_SELECTION_TOOL() :
106         PCB_TOOL_BASE( "pcbnew.InteractiveSelection" ),
107         m_frame( nullptr ),
108         m_nonModifiedCursor( KICURSOR::ARROW ),
109         m_enteredGroup( nullptr ),
110         m_priv( std::make_unique<PRIV>() )
111 {
112     m_filter.lockedItems = false;
113     m_filter.footprints  = true;
114     m_filter.text        = true;
115     m_filter.tracks      = true;
116     m_filter.vias        = true;
117     m_filter.pads        = true;
118     m_filter.graphics    = true;
119     m_filter.zones       = true;
120     m_filter.keepouts    = true;
121     m_filter.dimensions  = true;
122     m_filter.otherItems  = true;
123 }
124 
125 
~PCB_SELECTION_TOOL()126 PCB_SELECTION_TOOL::~PCB_SELECTION_TOOL()
127 {
128     getView()->Remove( &m_selection );
129     getView()->Remove( &m_enteredGroupOverlay );
130 
131     Disconnect( wxEVT_TIMER, wxTimerEventHandler( PCB_SELECTION_TOOL::onDisambiguationExpire ), nullptr, this );
132 }
133 
134 
Init()135 bool PCB_SELECTION_TOOL::Init()
136 {
137     auto frame = getEditFrame<PCB_BASE_FRAME>();
138 
139     if( frame && ( frame->IsType( FRAME_FOOTPRINT_VIEWER )
140                    || frame->IsType( FRAME_FOOTPRINT_VIEWER_MODAL ) ) )
141     {
142         frame->AddStandardSubMenus( m_menu );
143         return true;
144     }
145 
146     auto selectMenu = std::make_shared<SELECT_MENU>();
147     selectMenu->SetTool( this );
148     m_menu.AddSubMenu( selectMenu );
149 
150     auto& menu = m_menu.GetMenu();
151 
152     auto activeToolCondition =
153             [ frame ] ( const SELECTION& aSel )
154             {
155                 return !frame->ToolStackIsEmpty();
156             };
157 
158     auto inGroupCondition =
159             [this] ( const SELECTION& )
160             {
161                 return m_enteredGroup != nullptr;
162             };
163 
164     if( frame && frame->IsType( FRAME_PCB_EDITOR ) )
165     {
166         menu.AddMenu( selectMenu.get(), SELECTION_CONDITIONS::NotEmpty  );
167         menu.AddSeparator( 1000 );
168     }
169 
170     // "Cancel" goes at the top of the context menu when a tool is active
171     menu.AddItem( ACTIONS::cancelInteractive, activeToolCondition, 1 );
172     menu.AddItem( PCB_ACTIONS::groupLeave,    inGroupCondition,    1 );
173     menu.AddSeparator( 1 );
174 
175     if( frame )
176         frame->AddStandardSubMenus( m_menu );
177 
178     m_disambiguateTimer.SetOwner( this );
179     Connect( wxEVT_TIMER, wxTimerEventHandler( PCB_SELECTION_TOOL::onDisambiguationExpire ), nullptr, this );
180 
181     return true;
182 }
183 
184 
Reset(RESET_REASON aReason)185 void PCB_SELECTION_TOOL::Reset( RESET_REASON aReason )
186 {
187     m_frame = getEditFrame<PCB_BASE_FRAME>();
188 
189     if( m_enteredGroup )
190         ExitGroup();
191 
192     if( aReason == TOOL_BASE::MODEL_RELOAD )
193     {
194         // Deselect any item being currently in edit, to avoid unexpected behavior
195         // and remove pointers to the selected items from containers
196         // without changing their properties (as they are already deleted
197         // while a new board is loaded)
198         ClearSelection( true );
199 
200         getView()->GetPainter()->GetSettings()->SetHighlight( false );
201     }
202     else
203     {
204         // Restore previous properties of selected items and remove them from containers
205         ClearSelection( true );
206     }
207 
208     // Reinsert the VIEW_GROUP, in case it was removed from the VIEW
209     view()->Remove( &m_selection );
210     view()->Add( &m_selection );
211 
212     view()->Remove( &m_enteredGroupOverlay );
213     view()->Add( &m_enteredGroupOverlay );
214 }
215 
216 
onDisambiguationExpire(wxTimerEvent & aEvent)217 void PCB_SELECTION_TOOL::onDisambiguationExpire( wxTimerEvent& aEvent )
218 {
219     m_toolMgr->ProcessEvent( EVENTS::DisambiguatePoint );
220 }
221 
222 
OnIdle(wxIdleEvent & aEvent)223 void PCB_SELECTION_TOOL::OnIdle( wxIdleEvent& aEvent )
224 {
225     if( m_frame->ToolStackIsEmpty() && !m_multiple )
226     {
227         wxMouseState keyboardState = wxGetMouseState();
228 
229         setModifiersState( keyboardState.ShiftDown(), keyboardState.ControlDown(),
230                            keyboardState.AltDown() );
231 
232         if( m_additive )
233             m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::ADD );
234         else if( m_subtractive )
235             m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::SUBTRACT );
236         else if( m_exclusive_or )
237             m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::XOR );
238         else
239             m_frame->GetCanvas()->SetCurrentCursor( m_nonModifiedCursor );
240     }
241 }
242 
243 
Main(const TOOL_EVENT & aEvent)244 int PCB_SELECTION_TOOL::Main( const TOOL_EVENT& aEvent )
245 {
246     // Main loop: keep receiving events
247     while( TOOL_EVENT* evt = Wait() )
248     {
249         MOUSE_DRAG_ACTION dragAction = m_frame->GetDragAction();
250         TRACK_DRAG_ACTION trackDragAction = m_frame->Settings().m_TrackDragAction;
251 
252         // on left click, a selection is made, depending on modifiers ALT, SHIFT, CTRL:
253         setModifiersState( evt->Modifier( MD_SHIFT ), evt->Modifier( MD_CTRL ),
254                            evt->Modifier( MD_ALT ) );
255 
256         bool modifier_enabled = m_subtractive || m_additive || m_exclusive_or;
257         PCB_BASE_FRAME* frame = getEditFrame<PCB_BASE_FRAME>();
258         bool brd_editor = frame && frame->IsType( FRAME_PCB_EDITOR );
259         ROUTER_TOOL* router = m_toolMgr->GetTool<ROUTER_TOOL>();
260 
261         // If the router tool is active, don't override
262         if( router && router->IsToolActive() && router->RoutingInProgress() )
263         {
264             evt->SetPassEvent();
265         }
266         else if( evt->IsMouseDown( BUT_LEFT ) )
267         {
268             // Avoid triggering when running under other tools
269             PCB_POINT_EDITOR *pt_tool = m_toolMgr->GetTool<PCB_POINT_EDITOR>();
270 
271             if( m_frame->ToolStackIsEmpty() && pt_tool && !pt_tool->HasPoint() )
272             {
273                 m_originalCursor = m_toolMgr->GetMousePosition();
274                 m_disambiguateTimer.StartOnce( 500 );
275             }
276         }
277         else if( evt->IsClick( BUT_LEFT ) )
278         {
279             // If there is no disambiguation, this routine is still running and will
280             // register a `click` event when released
281             if( m_disambiguateTimer.IsRunning() )
282             {
283                 m_disambiguateTimer.Stop();
284 
285                 // Single click? Select single object
286                 if( m_highlight_modifier && brd_editor )
287                     m_toolMgr->RunAction( PCB_ACTIONS::highlightNet, true );
288                 else
289                 {
290                     m_frame->FocusOnItem( nullptr );
291                     selectPoint( evt->Position() );
292                 }
293             }
294 
295             m_canceledMenu = false;
296         }
297         else if( evt->IsClick( BUT_RIGHT ) )
298         {
299             m_disambiguateTimer.Stop();
300 
301             // Right click? if there is any object - show the context menu
302             bool selectionCancelled = false;
303 
304             if( m_selection.Empty() )
305             {
306                 selectPoint( evt->Position(), false, &selectionCancelled );
307                 m_selection.SetIsHover( true );
308             }
309 
310             if( !selectionCancelled )
311                 m_menu.ShowContextMenu( m_selection );
312         }
313         else if( evt->IsDblClick( BUT_LEFT ) )
314         {
315             m_disambiguateTimer.Stop();
316 
317             // Double click? Display the properties window
318             m_frame->FocusOnItem( nullptr );
319 
320             if( m_selection.Empty() )
321                 selectPoint( evt->Position() );
322 
323             if( m_selection.GetSize() == 1 && m_selection[0]->Type() == PCB_GROUP_T )
324             {
325                 EnterGroup();
326             }
327             else
328             {
329                 m_toolMgr->RunAction( PCB_ACTIONS::properties, true );
330             }
331         }
332         else if( evt->IsDblClick( BUT_MIDDLE ) )
333         {
334             // Middle double click?  Do zoom to fit or zoom to objects
335             if( evt->Modifier( MD_CTRL ) ) // Is CTRL key down?
336                 m_toolMgr->RunAction( ACTIONS::zoomFitObjects, true );
337             else
338                 m_toolMgr->RunAction( ACTIONS::zoomFitScreen, true );
339         }
340         else if( evt->IsDrag( BUT_LEFT ) )
341         {
342             m_disambiguateTimer.Stop();
343 
344             // Is another tool already moving a new object?  Don't allow a drag start
345             if( !m_selection.Empty() && m_selection[0]->HasFlag( IS_NEW | IS_MOVING ) )
346             {
347                 evt->SetPassEvent();
348                 continue;
349             }
350 
351             // Drag with LMB? Select multiple objects (or at least draw a selection box)
352             // or drag them
353             m_frame->FocusOnItem( nullptr );
354             m_toolMgr->ProcessEvent( EVENTS::InhibitSelectionEditing );
355 
356             if( modifier_enabled || dragAction == MOUSE_DRAG_ACTION::SELECT )
357             {
358                 selectMultiple();
359             }
360             else if( m_selection.Empty() && dragAction != MOUSE_DRAG_ACTION::DRAG_ANY )
361             {
362                 selectMultiple();
363             }
364             else
365             {
366                 // Don't allow starting a drag from a zone filled area that isn't already selected
367                 auto zoneFilledAreaFilter =
368                         []( const VECTOR2I& aWhere, GENERAL_COLLECTOR& aCollector,
369                             PCB_SELECTION_TOOL* aTool )
370                         {
371                             wxPoint location = wxPoint( aWhere );
372                             int     accuracy = KiROUND( 5 * aCollector.GetGuide()->OnePixelInIU() );
373                             std::set<EDA_ITEM*> remove;
374 
375                             for( EDA_ITEM* item : aCollector )
376                             {
377                                 if( item->Type() == PCB_ZONE_T || item->Type() == PCB_FP_ZONE_T )
378                                 {
379                                     ZONE* zone = static_cast<ZONE*>( item );
380 
381                                     if( !zone->HitTestForCorner( location, accuracy * 2 ) &&
382                                         !zone->HitTestForEdge( location, accuracy ) )
383                                         remove.insert( zone );
384                                 }
385                             }
386 
387                             for( EDA_ITEM* item : remove )
388                                 aCollector.Remove( item );
389                         };
390 
391                 // Selection is empty? try to start dragging the item under the point where drag
392                 // started
393                 if( m_selection.Empty() && selectCursor( false, zoneFilledAreaFilter ) )
394                     m_selection.SetIsHover( true );
395 
396                 // Check if dragging has started within any of selected items bounding box.
397                 // We verify "HasPosition()" first to protect against edge case involving
398                 // moving off menus that causes problems (issue #5250)
399                 if( evt->HasPosition() && selectionContains( evt->Position() ) )
400                 {
401                     // Yes -> run the move tool and wait till it finishes
402                     PCB_TRACK* track = dynamic_cast<PCB_TRACK*>( m_selection.GetItem( 0 ) );
403 
404                     // If there is only item in the selection and it's a track, then we need
405                     // to route it.
406                     bool doRouting = ( track && ( 1 == m_selection.GetSize() ) );
407 
408                     if( doRouting && trackDragAction == TRACK_DRAG_ACTION::DRAG )
409                         m_toolMgr->RunAction( PCB_ACTIONS::drag45Degree, true );
410                     else if( doRouting && trackDragAction == TRACK_DRAG_ACTION::DRAG_FREE_ANGLE )
411                         m_toolMgr->RunAction( PCB_ACTIONS::dragFreeAngle, true );
412                     else
413                         m_toolMgr->RunAction( PCB_ACTIONS::move, true );
414                 }
415                 else
416                 {
417                     // No -> drag a selection box
418                     selectMultiple();
419                 }
420             }
421         }
422         else if( evt->IsCancel() )
423         {
424             m_disambiguateTimer.Stop();
425             m_frame->FocusOnItem( nullptr );
426 
427             if( m_enteredGroup )
428                 ExitGroup();
429 
430             ClearSelection();
431         }
432         else
433         {
434             evt->SetPassEvent();
435         }
436 
437 
438         if( m_frame->ToolStackIsEmpty() )
439         {
440             // move cursor prediction
441             if( !modifier_enabled
442                     && dragAction == MOUSE_DRAG_ACTION::DRAG_SELECTED
443                     && !m_selection.Empty()
444                     && evt->HasPosition()
445                     && selectionContains( evt->Position() ) )
446             {
447                 m_nonModifiedCursor = KICURSOR::MOVING;
448             }
449             else
450             {
451                 m_nonModifiedCursor = KICURSOR::ARROW;
452             }
453         }
454     }
455 
456     // Shutting down; clear the selection
457     m_selection.Clear();
458     m_disambiguateTimer.Stop();
459 
460     return 0;
461 }
462 
463 
EnterGroup()464 void PCB_SELECTION_TOOL::EnterGroup()
465 {
466     wxCHECK_RET( m_selection.GetSize() == 1 && m_selection[0]->Type() == PCB_GROUP_T,
467                  "EnterGroup called when selection is not a single group" );
468     PCB_GROUP* aGroup = static_cast<PCB_GROUP*>( m_selection[0] );
469 
470     if( m_enteredGroup != nullptr )
471         ExitGroup();
472 
473     ClearSelection();
474     m_enteredGroup = aGroup;
475     m_enteredGroup->SetFlags( ENTERED );
476     m_enteredGroup->RunOnChildren( [&]( BOARD_ITEM* titem )
477                                    {
478                                        select( titem );
479                                    } );
480 
481     m_enteredGroupOverlay.Add( m_enteredGroup );
482 }
483 
484 
ExitGroup(bool aSelectGroup)485 void PCB_SELECTION_TOOL::ExitGroup( bool aSelectGroup )
486 {
487     // Only continue if there is a group entered
488     if( m_enteredGroup == nullptr )
489         return;
490 
491     m_enteredGroup->ClearFlags( ENTERED );
492     ClearSelection();
493 
494     if( aSelectGroup )
495         select( m_enteredGroup );
496 
497     m_enteredGroupOverlay.Clear();
498     m_enteredGroup = nullptr;
499 }
500 
501 
GetSelection()502 PCB_SELECTION& PCB_SELECTION_TOOL::GetSelection()
503 {
504     return m_selection;
505 }
506 
507 
RequestSelection(CLIENT_SELECTION_FILTER aClientFilter,bool aConfirmLockedItems)508 PCB_SELECTION& PCB_SELECTION_TOOL::RequestSelection( CLIENT_SELECTION_FILTER aClientFilter,
509                                                      bool aConfirmLockedItems )
510 {
511     bool selectionEmpty = m_selection.Empty();
512     m_selection.SetIsHover( selectionEmpty );
513 
514     if( selectionEmpty )
515     {
516         m_toolMgr->RunAction( PCB_ACTIONS::selectionCursor, true, aClientFilter );
517         m_selection.ClearReferencePoint();
518     }
519 
520     if( aClientFilter )
521     {
522         enum DISPOSITION { BEFORE = 1, AFTER, BOTH };
523 
524         std::map<EDA_ITEM*, DISPOSITION> itemDispositions;
525         GENERAL_COLLECTOR                collector;
526 
527         for( EDA_ITEM* item : m_selection )
528         {
529             collector.Append( item );
530             itemDispositions[ item ] = BEFORE;
531         }
532 
533         aClientFilter( VECTOR2I(), collector, this );
534 
535         for( EDA_ITEM* item : collector )
536         {
537             if( itemDispositions.count( item ) )
538                 itemDispositions[ item ] = BOTH;
539             else
540                 itemDispositions[ item ] = AFTER;
541         }
542 
543         // Unhighlight the BEFORE items before highlighting the AFTER items.
544         // This is so that in the case of groups, if aClientFilter replaces a selection
545         // with the enclosing group, the unhighlight of the element doesn't undo the
546         // recursive highlighting of that element by the group.
547 
548         for( std::pair<EDA_ITEM* const, DISPOSITION> itemDisposition : itemDispositions )
549         {
550             BOARD_ITEM* item = static_cast<BOARD_ITEM*>( itemDisposition.first );
551             DISPOSITION disposition = itemDisposition.second;
552 
553             if( disposition == BEFORE )
554             {
555                 unhighlight( item, SELECTED, &m_selection );
556             }
557         }
558 
559         for( std::pair<EDA_ITEM* const, DISPOSITION> itemDisposition : itemDispositions )
560         {
561             BOARD_ITEM* item = static_cast<BOARD_ITEM*>( itemDisposition.first );
562             DISPOSITION disposition = itemDisposition.second;
563 
564             if( disposition == AFTER )
565             {
566                 highlight( item, SELECTED, &m_selection );
567             }
568             else if( disposition == BOTH )
569             {
570                 // nothing to do
571             }
572         }
573 
574         m_frame->GetCanvas()->ForceRefresh();
575     }
576 
577     if( aConfirmLockedItems )
578     {
579         std::vector<BOARD_ITEM*> lockedItems;
580 
581         for( EDA_ITEM* item : m_selection )
582         {
583             BOARD_ITEM* boardItem = static_cast<BOARD_ITEM*>( item );
584 
585             if( boardItem->Type() == PCB_GROUP_T )
586             {
587                 PCB_GROUP* group = static_cast<PCB_GROUP*>( boardItem );
588                 bool       lockedDescendant = false;
589 
590                 group->RunOnDescendants(
591                         [&lockedDescendant]( BOARD_ITEM* child )
592                         {
593                             if( child->IsLocked() )
594                                 lockedDescendant = true;
595                         } );
596 
597                 if( lockedDescendant )
598                     lockedItems.push_back( group );
599             }
600             else if( boardItem->IsLocked() )
601             {
602                 lockedItems.push_back( boardItem );
603             }
604         }
605 
606         if( !lockedItems.empty() )
607         {
608             DIALOG_LOCKED_ITEMS_QUERY dlg( frame(), lockedItems.size() );
609 
610             switch( dlg.ShowModal() )
611             {
612             case wxID_OK:
613                 // remove locked items from selection
614                 for( BOARD_ITEM* item : lockedItems )
615                     unselect( item );
616 
617                 break;
618 
619             case wxID_CANCEL:
620                 // cancel operation
621                 ClearSelection();
622                 break;
623 
624             case wxID_APPLY:
625                 // continue with operation with current selection
626                 break;
627             }
628         }
629     }
630 
631     return m_selection;
632 }
633 
634 
getCollectorsGuide() const635 const GENERAL_COLLECTORS_GUIDE PCB_SELECTION_TOOL::getCollectorsGuide() const
636 {
637     GENERAL_COLLECTORS_GUIDE guide( board()->GetVisibleLayers(),
638                                     (PCB_LAYER_ID) view()->GetTopLayer(), view() );
639 
640     bool padsDisabled = !board()->IsElementVisible( LAYER_PADS );
641 
642     // account for the globals
643     guide.SetIgnoreMTextsMarkedNoShow( ! board()->IsElementVisible( LAYER_MOD_TEXT_INVISIBLE ) );
644     guide.SetIgnoreMTextsOnBack( ! board()->IsElementVisible( LAYER_MOD_TEXT ) );
645     guide.SetIgnoreMTextsOnFront( ! board()->IsElementVisible( LAYER_MOD_TEXT ) );
646     guide.SetIgnoreModulesOnBack( ! board()->IsElementVisible( LAYER_MOD_BK ) );
647     guide.SetIgnoreModulesOnFront( ! board()->IsElementVisible( LAYER_MOD_FR ) );
648     guide.SetIgnorePadsOnBack( padsDisabled || ! board()->IsElementVisible( LAYER_PAD_BK ) );
649     guide.SetIgnorePadsOnFront( padsDisabled || ! board()->IsElementVisible( LAYER_PAD_FR ) );
650     guide.SetIgnoreThroughHolePads( padsDisabled || ! board()->IsElementVisible( LAYER_PADS_TH ) );
651     guide.SetIgnoreModulesVals( ! board()->IsElementVisible( LAYER_MOD_VALUES ) );
652     guide.SetIgnoreModulesRefs( ! board()->IsElementVisible( LAYER_MOD_REFERENCES ) );
653     guide.SetIgnoreThroughVias( ! board()->IsElementVisible( LAYER_VIAS ) );
654     guide.SetIgnoreBlindBuriedVias( ! board()->IsElementVisible( LAYER_VIAS ) );
655     guide.SetIgnoreMicroVias( ! board()->IsElementVisible( LAYER_VIAS ) );
656     guide.SetIgnoreTracks( ! board()->IsElementVisible( LAYER_TRACKS ) );
657 
658     return guide;
659 }
660 
661 
selectPoint(const VECTOR2I & aWhere,bool aOnDrag,bool * aSelectionCancelledFlag,CLIENT_SELECTION_FILTER aClientFilter)662 bool PCB_SELECTION_TOOL::selectPoint( const VECTOR2I& aWhere, bool aOnDrag,
663                                       bool* aSelectionCancelledFlag,
664                                       CLIENT_SELECTION_FILTER aClientFilter )
665 {
666     GENERAL_COLLECTORS_GUIDE   guide = getCollectorsGuide();
667     GENERAL_COLLECTOR          collector;
668     const PCB_DISPLAY_OPTIONS& displayOpts = m_frame->GetDisplayOptions();
669 
670     guide.SetIgnoreZoneFills( displayOpts.m_ZoneDisplayMode != ZONE_DISPLAY_MODE::SHOW_FILLED );
671 
672     if( m_enteredGroup && !m_enteredGroup->GetBoundingBox().Contains( (wxPoint) aWhere ) )
673         ExitGroup();
674 
675     collector.Collect( board(), m_isFootprintEditor ? GENERAL_COLLECTOR::FootprintItems
676                                                     : GENERAL_COLLECTOR::AllBoardItems,
677                        (wxPoint) aWhere, guide );
678 
679     // Remove unselectable items
680     for( int i = collector.GetCount() - 1; i >= 0; --i )
681     {
682         if( !Selectable( collector[ i ] ) || ( aOnDrag && collector[i]->IsLocked() ) )
683             collector.Remove( i );
684     }
685 
686     m_selection.ClearReferencePoint();
687 
688     // Allow the client to do tool- or action-specific filtering to see if we can get down
689     // to a single item
690     if( aClientFilter )
691         aClientFilter( aWhere, collector, this );
692 
693     FilterCollectorForHierarchy( collector, false );
694 
695     // Apply the stateful filter
696     FilterCollectedItems( collector, false );
697 
698     // Apply some ugly heuristics to avoid disambiguation menus whenever possible
699     if( collector.GetCount() > 1 && !m_skip_heuristics )
700         GuessSelectionCandidates( collector, aWhere );
701 
702     // If still more than one item we're going to have to ask the user.
703     if( collector.GetCount() > 1 )
704     {
705         if( aOnDrag )
706             Wait( TOOL_EVENT( TC_ANY, TA_MOUSE_UP, BUT_LEFT ) );
707 
708         if( !doSelectionMenu( &collector ) )
709         {
710             if( aSelectionCancelledFlag )
711                 *aSelectionCancelledFlag = true;
712 
713             return false;
714         }
715     }
716 
717     bool anyAdded      = false;
718     bool anySubtracted = false;
719 
720     if( !m_additive && !m_subtractive && !m_exclusive_or )
721     {
722         if( m_selection.GetSize() > 0 )
723         {
724             ClearSelection( true /*quiet mode*/ );
725             anySubtracted = true;
726         }
727     }
728 
729     if( collector.GetCount() > 0 )
730     {
731         for( int i = 0; i < collector.GetCount(); ++i )
732         {
733             if( m_subtractive || ( m_exclusive_or && collector[i]->IsSelected() ) )
734             {
735                 unselect( collector[i] );
736                 anySubtracted = true;
737             }
738             else
739             {
740                 select( collector[i] );
741                 anyAdded = true;
742             }
743         }
744     }
745 
746     if( anyAdded )
747     {
748         m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
749         return true;
750     }
751     else if( anySubtracted )
752     {
753         m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent );
754         return true;
755     }
756 
757     return false;
758 }
759 
760 
selectCursor(bool aForceSelect,CLIENT_SELECTION_FILTER aClientFilter)761 bool PCB_SELECTION_TOOL::selectCursor( bool aForceSelect, CLIENT_SELECTION_FILTER aClientFilter )
762 {
763     if( aForceSelect || m_selection.Empty() )
764     {
765         ClearSelection( true /*quiet mode*/ );
766         selectPoint( getViewControls()->GetCursorPosition( false ), false, nullptr, aClientFilter );
767     }
768 
769     return !m_selection.Empty();
770 }
771 
772 
773 // Some navigation actions are allowed in selectMultiple
774 const TOOL_ACTION* allowedActions[] = { &ACTIONS::panUp,          &ACTIONS::panDown,
775                                         &ACTIONS::panLeft,        &ACTIONS::panRight,
776                                         &ACTIONS::cursorUp,       &ACTIONS::cursorDown,
777                                         &ACTIONS::cursorLeft,     &ACTIONS::cursorRight,
778                                         &ACTIONS::cursorUpFast,   &ACTIONS::cursorDownFast,
779                                         &ACTIONS::cursorLeftFast, &ACTIONS::cursorRightFast,
780                                         &ACTIONS::zoomIn,         &ACTIONS::zoomOut,
781                                         &ACTIONS::zoomInCenter,   &ACTIONS::zoomOutCenter,
782                                         &ACTIONS::zoomCenter,     &ACTIONS::zoomFitScreen,
783                                         &ACTIONS::zoomFitObjects, nullptr };
784 
785 
selectMultiple()786 bool PCB_SELECTION_TOOL::selectMultiple()
787 {
788     bool cancelled = false;     // Was the tool cancelled while it was running?
789     m_multiple = true;          // Multiple selection mode is active
790     KIGFX::VIEW* view = getView();
791 
792     KIGFX::PREVIEW::SELECTION_AREA area;
793     view->Add( &area );
794 
795     bool anyAdded = false;
796     bool anySubtracted = false;
797 
798     while( TOOL_EVENT* evt = Wait() )
799     {
800         int width = area.GetEnd().x - area.GetOrigin().x;
801 
802         /* Selection mode depends on direction of drag-selection:
803          * Left > Right : Select objects that are fully enclosed by selection
804          * Right > Left : Select objects that are crossed by selection
805          */
806         bool windowSelection = width >= 0 ? true : false;
807 
808         if( view->IsMirroredX() )
809             windowSelection = !windowSelection;
810 
811         m_frame->GetCanvas()->SetCurrentCursor( windowSelection ? KICURSOR::SELECT_WINDOW
812                                                                 : KICURSOR::SELECT_LASSO );
813 
814         if( evt->IsCancelInteractive() || evt->IsActivate() )
815         {
816             cancelled = true;
817             break;
818         }
819 
820         if( evt->IsDrag( BUT_LEFT ) )
821         {
822             if( !m_drag_additive && !m_drag_subtractive )
823             {
824                 if( m_selection.GetSize() > 0 )
825                 {
826                     anySubtracted = true;
827                     ClearSelection( true /*quiet mode*/ );
828                 }
829             }
830 
831             // Start drawing a selection box
832             area.SetOrigin( evt->DragOrigin() );
833             area.SetEnd( evt->Position() );
834             area.SetAdditive( m_drag_additive );
835             area.SetSubtractive( m_drag_subtractive );
836             area.SetExclusiveOr( false );
837 
838             view->SetVisible( &area, true );
839             view->Update( &area );
840             getViewControls()->SetAutoPan( true );
841         }
842 
843         if( evt->IsMouseUp( BUT_LEFT ) )
844         {
845             getViewControls()->SetAutoPan( false );
846 
847             // End drawing the selection box
848             view->SetVisible( &area, false );
849 
850             std::vector<KIGFX::VIEW::LAYER_ITEM_PAIR> candidates;
851             BOX2I selectionBox = area.ViewBBox();
852             view->Query( selectionBox, candidates );    // Get the list of nearby items
853 
854             int height = area.GetEnd().y - area.GetOrigin().y;
855 
856             // Construct an EDA_RECT to determine BOARD_ITEM selection
857             EDA_RECT selectionRect( (wxPoint) area.GetOrigin(), wxSize( width, height ) );
858 
859             selectionRect.Normalize();
860 
861             GENERAL_COLLECTOR collector;
862 
863             for( auto it = candidates.begin(), it_end = candidates.end(); it != it_end; ++it )
864             {
865                 BOARD_ITEM* item = static_cast<BOARD_ITEM*>( it->first );
866 
867                 if( item && Selectable( item ) && item->HitTest( selectionRect, windowSelection ) )
868                     collector.Append( item );
869             }
870 
871             // Apply the stateful filter
872             FilterCollectedItems( collector, true );
873 
874             FilterCollectorForHierarchy( collector, true );
875 
876             for( EDA_ITEM* i : collector )
877             {
878                 BOARD_ITEM* item = static_cast<BOARD_ITEM*>( i );
879 
880                 if( m_subtractive || ( m_exclusive_or && item->IsSelected() ) )
881                 {
882                     unselect( item );
883                     anySubtracted = true;
884                 }
885                 else
886                 {
887                     select( item );
888                     anyAdded = true;
889                 }
890             }
891 
892             m_selection.SetIsHover( false );
893 
894             // Inform other potentially interested tools
895             if( anyAdded )
896                 m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
897             else if( anySubtracted )
898                 m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent );
899 
900             break;  // Stop waiting for events
901         }
902 
903         // Allow some actions for navigation
904         for( int i = 0; allowedActions[i]; ++i )
905         {
906             if( evt->IsAction( allowedActions[i] ) )
907             {
908                 evt->SetPassEvent();
909                 break;
910             }
911         }
912     }
913 
914     getViewControls()->SetAutoPan( false );
915 
916     // Stop drawing the selection box
917     view->Remove( &area );
918     m_multiple = false;         // Multiple selection mode is inactive
919 
920     if( !cancelled )
921         m_selection.ClearReferencePoint();
922 
923     m_toolMgr->ProcessEvent( EVENTS::UninhibitSelectionEditing );
924 
925     return cancelled;
926 }
927 
928 
disambiguateCursor(const TOOL_EVENT & aEvent)929 int PCB_SELECTION_TOOL::disambiguateCursor( const TOOL_EVENT& aEvent )
930 {
931     m_skip_heuristics = true;
932     selectPoint( m_originalCursor, false, &m_canceledMenu );
933     m_skip_heuristics = false;
934 
935     return 0;
936 }
937 
938 
939 
CursorSelection(const TOOL_EVENT & aEvent)940 int PCB_SELECTION_TOOL::CursorSelection( const TOOL_EVENT& aEvent )
941 {
942     CLIENT_SELECTION_FILTER aClientFilter = aEvent.Parameter<CLIENT_SELECTION_FILTER>();
943 
944     selectCursor( false, aClientFilter );
945 
946     return 0;
947 }
948 
949 
ClearSelection(const TOOL_EVENT & aEvent)950 int PCB_SELECTION_TOOL::ClearSelection( const TOOL_EVENT& aEvent )
951 {
952     ClearSelection();
953 
954     return 0;
955 }
956 
957 
SelectItems(const TOOL_EVENT & aEvent)958 int PCB_SELECTION_TOOL::SelectItems( const TOOL_EVENT& aEvent )
959 {
960     std::vector<BOARD_ITEM*>* items = aEvent.Parameter<std::vector<BOARD_ITEM*>*>();
961 
962     if( items )
963     {
964         // Perform individual selection of each item before processing the event.
965         for( BOARD_ITEM* item : *items )
966             select( item );
967 
968         m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
969     }
970 
971     return 0;
972 }
973 
974 
SelectItem(const TOOL_EVENT & aEvent)975 int PCB_SELECTION_TOOL::SelectItem( const TOOL_EVENT& aEvent )
976 {
977     AddItemToSel( aEvent.Parameter<BOARD_ITEM*>() );
978     return 0;
979 }
980 
981 
SelectAll(const TOOL_EVENT & aEvent)982 int PCB_SELECTION_TOOL::SelectAll( const TOOL_EVENT& aEvent )
983 {
984     KIGFX::VIEW* view = getView();
985 
986     // hold all visible items
987     std::vector<KIGFX::VIEW::LAYER_ITEM_PAIR> selectedItems;
988 
989     // Filter the view items based on the selection box
990     BOX2I selectionBox;
991 
992     // Intermediate step to allow filtering against hierarchy
993     GENERAL_COLLECTOR collection;
994 
995     selectionBox.SetMaximum();
996     view->Query( selectionBox, selectedItems );         // Get the list of selected items
997 
998     for( const KIGFX::VIEW::LAYER_ITEM_PAIR& item_pair : selectedItems )
999     {
1000         BOARD_ITEM* item = static_cast<BOARD_ITEM*>( item_pair.first );
1001 
1002         if( !item || !Selectable( item ) || !itemPassesFilter( item, true ) )
1003             continue;
1004 
1005         collection.Append( item );
1006     }
1007 
1008     FilterCollectorForHierarchy( collection, true );
1009 
1010     for( EDA_ITEM* item : collection )
1011         select( static_cast<BOARD_ITEM*>( item ) );
1012 
1013     m_frame->GetCanvas()->ForceRefresh();
1014 
1015     return 0;
1016 }
1017 
1018 
AddItemToSel(BOARD_ITEM * aItem,bool aQuietMode)1019 void PCB_SELECTION_TOOL::AddItemToSel( BOARD_ITEM* aItem, bool aQuietMode )
1020 {
1021     if( aItem )
1022     {
1023         select( aItem );
1024 
1025         // Inform other potentially interested tools
1026         if( !aQuietMode )
1027             m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
1028     }
1029 }
1030 
1031 
UnselectItems(const TOOL_EVENT & aEvent)1032 int PCB_SELECTION_TOOL::UnselectItems( const TOOL_EVENT& aEvent )
1033 {
1034     std::vector<BOARD_ITEM*>* items = aEvent.Parameter<std::vector<BOARD_ITEM*>*>();
1035 
1036     if( items )
1037     {
1038         // Perform individual unselection of each item before processing the event
1039         for( auto item : *items )
1040             unselect( item );
1041 
1042         m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent );
1043     }
1044 
1045     return 0;
1046 }
1047 
1048 
UnselectItem(const TOOL_EVENT & aEvent)1049 int PCB_SELECTION_TOOL::UnselectItem( const TOOL_EVENT& aEvent )
1050 {
1051     RemoveItemFromSel( aEvent.Parameter<BOARD_ITEM*>() );
1052     return 0;
1053 }
1054 
1055 
RemoveItemFromSel(BOARD_ITEM * aItem,bool aQuietMode)1056 void PCB_SELECTION_TOOL::RemoveItemFromSel( BOARD_ITEM* aItem, bool aQuietMode )
1057 {
1058     if( aItem )
1059     {
1060         unselect( aItem );
1061 
1062         if( !aQuietMode )
1063         {
1064             // Inform other potentially interested tools
1065             m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent );
1066         }
1067     }
1068 }
1069 
1070 
BrightenItem(BOARD_ITEM * aItem)1071 void PCB_SELECTION_TOOL::BrightenItem( BOARD_ITEM* aItem )
1072 {
1073     highlight( aItem, BRIGHTENED );
1074 }
1075 
1076 
UnbrightenItem(BOARD_ITEM * aItem)1077 void PCB_SELECTION_TOOL::UnbrightenItem( BOARD_ITEM* aItem )
1078 {
1079     unhighlight( aItem, BRIGHTENED );
1080 }
1081 
1082 
connectedItemFilter(const VECTOR2I &,GENERAL_COLLECTOR & aCollector,PCB_SELECTION_TOOL * sTool)1083 void connectedItemFilter( const VECTOR2I&, GENERAL_COLLECTOR& aCollector,
1084                           PCB_SELECTION_TOOL* sTool )
1085 {
1086     // Narrow the collection down to a single BOARD_CONNECTED_ITEM for each represented net.
1087     // All other items types are removed.
1088     std::set<int> representedNets;
1089 
1090     for( int i = aCollector.GetCount() - 1; i >= 0; i-- )
1091     {
1092         BOARD_CONNECTED_ITEM* item = dynamic_cast<BOARD_CONNECTED_ITEM*>( aCollector[i] );
1093         if( !item )
1094             aCollector.Remove( i );
1095         else if ( representedNets.count( item->GetNetCode() ) )
1096             aCollector.Remove( i );
1097         else
1098             representedNets.insert( item->GetNetCode() );
1099     }
1100 }
1101 
1102 
expandConnection(const TOOL_EVENT & aEvent)1103 int PCB_SELECTION_TOOL::expandConnection( const TOOL_EVENT& aEvent )
1104 {
1105     unsigned initialCount = 0;
1106 
1107     for( const EDA_ITEM* item : m_selection.GetItems() )
1108     {
1109         if( dynamic_cast<const BOARD_CONNECTED_ITEM*>( item ) )
1110             initialCount++;
1111     }
1112 
1113     if( initialCount == 0 )
1114         selectCursor( true, connectedItemFilter );
1115 
1116     for( STOP_CONDITION stopCondition : { STOP_AT_JUNCTION, STOP_AT_PAD, STOP_NEVER } )
1117     {
1118         // copy the selection, since we're going to iterate and modify
1119         std::deque<EDA_ITEM*> selectedItems = m_selection.GetItems();
1120 
1121         for( EDA_ITEM* item : selectedItems )
1122             item->ClearTempFlags();
1123 
1124         for( EDA_ITEM* item : selectedItems )
1125         {
1126             PCB_TRACK* trackItem = dynamic_cast<PCB_TRACK*>( item );
1127 
1128             // Track items marked SKIP_STRUCT have already been visited
1129             if( trackItem && !( trackItem->GetFlags() & SKIP_STRUCT ) )
1130                 selectConnectedTracks( *trackItem, stopCondition );
1131         }
1132 
1133         if( m_selection.GetItems().size() > initialCount )
1134             break;
1135     }
1136 
1137     // Inform other potentially interested tools
1138     if( m_selection.Size() > 0 )
1139         m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
1140 
1141     return 0;
1142 }
1143 
1144 
selectConnectedTracks(BOARD_CONNECTED_ITEM & aStartItem,STOP_CONDITION aStopCondition)1145 void PCB_SELECTION_TOOL::selectConnectedTracks( BOARD_CONNECTED_ITEM& aStartItem,
1146                                                 STOP_CONDITION aStopCondition )
1147 {
1148     constexpr KICAD_T      types[] = { PCB_TRACE_T, PCB_ARC_T, PCB_VIA_T, PCB_PAD_T, EOT };
1149     constexpr PCB_LAYER_ID ALL_LAYERS = UNDEFINED_LAYER;
1150 
1151     auto connectivity = board()->GetConnectivity();
1152     auto connectedItems = connectivity->GetConnectedItems( &aStartItem, types, true );
1153 
1154     std::map<wxPoint, std::vector<PCB_TRACK*>> trackMap;
1155     std::map<wxPoint, PCB_VIA*>                viaMap;
1156     std::map<wxPoint, PAD*>                    padMap;
1157 
1158     // Build maps of connected items
1159     for( BOARD_CONNECTED_ITEM* item : connectedItems )
1160     {
1161         switch( item->Type() )
1162         {
1163         case PCB_ARC_T:
1164         case PCB_TRACE_T:
1165         {
1166             PCB_TRACK* track = static_cast<PCB_TRACK*>( item );
1167             trackMap[ track->GetStart() ].push_back( track );
1168             trackMap[ track->GetEnd() ].push_back( track );
1169             break;
1170         }
1171 
1172         case PCB_VIA_T:
1173         {
1174             PCB_VIA* via = static_cast<PCB_VIA*>( item );
1175             viaMap[ via->GetStart() ] = via;
1176             break;
1177         }
1178 
1179         case PCB_PAD_T:
1180         {
1181             PAD* pad = static_cast<PAD*>( item );
1182             padMap[ pad->GetPosition() ] = pad;
1183             break;
1184         }
1185 
1186         default:
1187             break;
1188         }
1189 
1190         item->ClearFlags( TEMP_SELECTED );
1191     }
1192 
1193     std::vector< std::pair<wxPoint, PCB_LAYER_ID> > activePts;
1194 
1195     // Set up the initial active points
1196     switch( aStartItem.Type() )
1197     {
1198     case PCB_ARC_T:
1199     case PCB_TRACE_T:
1200     {
1201         PCB_TRACK* track = static_cast<PCB_TRACK*>( &aStartItem );
1202 
1203         activePts.push_back( { track->GetStart(), track->GetLayer() } );
1204         activePts.push_back( { track->GetEnd(), track->GetLayer() } );
1205     }
1206         break;
1207 
1208     case PCB_VIA_T:
1209         activePts.push_back( { aStartItem.GetPosition(), ALL_LAYERS } );
1210         break;
1211 
1212     case PCB_PAD_T:
1213         activePts.push_back( { aStartItem.GetPosition(), ALL_LAYERS } );
1214         break;
1215 
1216     default:
1217         break;
1218     }
1219 
1220     bool expand = true;
1221     int  failSafe = 0;
1222 
1223     // Iterative push from all active points
1224     while( expand && failSafe++ < 100000 )
1225     {
1226         expand = false;
1227 
1228         for( int i = activePts.size() - 1; i >= 0; --i )
1229         {
1230             wxPoint      pt = activePts[i].first;
1231             PCB_LAYER_ID layer = activePts[i].second;
1232             size_t       pt_count = 0;
1233 
1234             for( PCB_TRACK* track : trackMap[pt] )
1235             {
1236                 if( layer == ALL_LAYERS || layer == track->GetLayer() )
1237                     pt_count++;
1238             }
1239 
1240             if( aStopCondition == STOP_AT_JUNCTION )
1241             {
1242                 if( pt_count > 2
1243                         || ( viaMap.count( pt ) && layer != ALL_LAYERS )
1244                         || ( padMap.count( pt ) && layer != ALL_LAYERS ) )
1245                 {
1246                     activePts.erase( activePts.begin() + i );
1247                     continue;
1248                 }
1249             }
1250             else if( aStopCondition == STOP_AT_PAD )
1251             {
1252                 if( padMap.count( pt ) )
1253                 {
1254                     activePts.erase( activePts.begin() + i );
1255                     continue;
1256                 }
1257             }
1258 
1259             if( padMap.count( pt ) )
1260             {
1261                 PAD* pad = padMap[ pt ];
1262 
1263                 if( !( pad->GetFlags() & TEMP_SELECTED ) )
1264                 {
1265                     pad->SetFlags( TEMP_SELECTED );
1266                     activePts.push_back( { pad->GetPosition(), ALL_LAYERS } );
1267                     expand = true;
1268                 }
1269             }
1270 
1271             for( PCB_TRACK* track : trackMap[ pt ] )
1272             {
1273                 if( layer != ALL_LAYERS && track->GetLayer() != layer )
1274                     continue;
1275 
1276                 if( !track->IsSelected() )
1277                 {
1278                     select( track );
1279 
1280                     if( track->GetStart() == pt )
1281                         activePts.push_back( { track->GetEnd(), track->GetLayer() } );
1282                     else
1283                         activePts.push_back( { track->GetStart(), track->GetLayer() } );
1284 
1285                     expand = true;
1286                 }
1287             }
1288 
1289             if( viaMap.count( pt ) )
1290             {
1291                 PCB_VIA* via = viaMap[ pt ];
1292 
1293                 if( !via->IsSelected() )
1294                 {
1295                     select( via );
1296                     activePts.push_back( { via->GetPosition(), ALL_LAYERS } );
1297                     expand = true;
1298                 }
1299             }
1300 
1301             activePts.erase( activePts.begin() + i );
1302         }
1303     }
1304 }
1305 
1306 
selectAllItemsOnNet(int aNetCode,bool aSelect)1307 void PCB_SELECTION_TOOL::selectAllItemsOnNet( int aNetCode, bool aSelect )
1308 {
1309     constexpr KICAD_T types[] = { PCB_TRACE_T, PCB_ARC_T, PCB_VIA_T, EOT };
1310     auto connectivity = board()->GetConnectivity();
1311 
1312     for( BOARD_CONNECTED_ITEM* item : connectivity->GetNetItems( aNetCode, types ) )
1313     {
1314         if( itemPassesFilter( item, true ) )
1315             aSelect ? select( item ) : unselect( item );
1316     }
1317 }
1318 
1319 
selectNet(const TOOL_EVENT & aEvent)1320 int PCB_SELECTION_TOOL::selectNet( const TOOL_EVENT& aEvent )
1321 {
1322     bool select = aEvent.IsAction( &PCB_ACTIONS::selectNet );
1323 
1324     // If we've been passed an argument, just select that netcode1
1325     int netcode = aEvent.Parameter<intptr_t>();
1326 
1327     if( netcode > 0 )
1328     {
1329         selectAllItemsOnNet( netcode, select );
1330         return 0;
1331     }
1332 
1333     if( !selectCursor() )
1334         return 0;
1335 
1336     // copy the selection, since we're going to iterate and modify
1337     auto selection = m_selection.GetItems();
1338 
1339     for( EDA_ITEM* i : selection )
1340     {
1341         BOARD_CONNECTED_ITEM* connItem = dynamic_cast<BOARD_CONNECTED_ITEM*>( i );
1342 
1343         if( connItem )
1344             selectAllItemsOnNet( connItem->GetNetCode(), select );
1345     }
1346 
1347     // Inform other potentially interested tools
1348     if( m_selection.Size() > 0 )
1349         m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
1350 
1351     return 0;
1352 }
1353 
1354 
selectAllItemsOnSheet(wxString & aSheetPath)1355 void PCB_SELECTION_TOOL::selectAllItemsOnSheet( wxString& aSheetPath )
1356 {
1357     std::list<FOOTPRINT*> footprintList;
1358 
1359     // store all footprints that are on that sheet path
1360     for( FOOTPRINT* footprint : board()->Footprints() )
1361     {
1362         if( footprint == nullptr )
1363             continue;
1364 
1365         wxString footprint_path = footprint->GetPath().AsString().BeforeLast('/');
1366 
1367         if( aSheetPath.IsEmpty() )
1368             aSheetPath += '/';
1369 
1370         if( footprint_path == aSheetPath )
1371             footprintList.push_back( footprint );
1372     }
1373 
1374     // Generate a list of all pads, and of all nets they belong to.
1375     std::list<int>  netcodeList;
1376     std::list<PAD*> padList;
1377 
1378     for( FOOTPRINT* footprint : footprintList )
1379     {
1380         for( PAD* pad : footprint->Pads() )
1381         {
1382             if( pad->IsConnected() )
1383             {
1384                 netcodeList.push_back( pad->GetNetCode() );
1385                 padList.push_back( pad );
1386             }
1387         }
1388     }
1389 
1390     // remove all duplicates
1391     netcodeList.sort();
1392     netcodeList.unique();
1393 
1394     for( PAD* pad : padList )
1395         selectConnectedTracks( *pad, STOP_NEVER );
1396 
1397     // now we need to find all footprints that are connected to each of these nets then we need
1398     // to determine if these footprints are in the list of footprints belonging to this sheet
1399     std::list<int> removeCodeList;
1400     constexpr KICAD_T padType[] = { PCB_PAD_T, EOT };
1401 
1402     for( int netCode : netcodeList )
1403     {
1404         for( BOARD_CONNECTED_ITEM* mitem : board()->GetConnectivity()->GetNetItems( netCode,
1405                                                                                     padType ) )
1406         {
1407             if( mitem->Type() == PCB_PAD_T && !alg::contains( footprintList, mitem->GetParent() ) )
1408             {
1409                 // if we cannot find the footprint of the pad in the footprintList then we can
1410                 // assume that that footprint is not located in the same schematic, therefore
1411                 // invalidate this netcode.
1412                 removeCodeList.push_back( netCode );
1413                 break;
1414             }
1415         }
1416     }
1417 
1418     // remove all duplicates
1419     removeCodeList.sort();
1420     removeCodeList.unique();
1421 
1422     for( int removeCode : removeCodeList )
1423     {
1424         netcodeList.remove( removeCode );
1425     }
1426 
1427     std::list<BOARD_CONNECTED_ITEM*> localConnectionList;
1428     constexpr KICAD_T trackViaType[] = { PCB_TRACE_T, PCB_ARC_T, PCB_VIA_T, EOT };
1429 
1430     for( int netCode : netcodeList )
1431     {
1432         for( BOARD_CONNECTED_ITEM* item : board()->GetConnectivity()->GetNetItems( netCode,
1433                                                                                    trackViaType ) )
1434             localConnectionList.push_back( item );
1435     }
1436 
1437     for( BOARD_ITEM* i : footprintList )
1438     {
1439         if( i != nullptr )
1440             select( i );
1441     }
1442 
1443     for( BOARD_CONNECTED_ITEM* i : localConnectionList )
1444     {
1445         if( i != nullptr )
1446             select( i );
1447     }
1448 }
1449 
1450 
zoomFitSelection()1451 void PCB_SELECTION_TOOL::zoomFitSelection()
1452 {
1453     // Should recalculate the view to zoom in on the selection.
1454     auto selectionBox = m_selection.GetBoundingBox();
1455     auto view = getView();
1456 
1457     VECTOR2D screenSize = view->ToWorld( m_frame->GetCanvas()->GetClientSize(), false );
1458     screenSize.x = std::max( 10.0, screenSize.x );
1459     screenSize.y = std::max( 10.0, screenSize.y );
1460 
1461     if( selectionBox.GetWidth() != 0  || selectionBox.GetHeight() != 0 )
1462     {
1463         VECTOR2D vsize = selectionBox.GetSize();
1464         double scale = view->GetScale() / std::max( fabs( vsize.x / screenSize.x ),
1465                 fabs( vsize.y / screenSize.y ) );
1466         view->SetScale( scale );
1467         view->SetCenter( selectionBox.Centre() );
1468         view->Add( &m_selection );
1469     }
1470 
1471     m_frame->GetCanvas()->ForceRefresh();
1472 }
1473 
1474 
selectSheetContents(const TOOL_EVENT & aEvent)1475 int PCB_SELECTION_TOOL::selectSheetContents( const TOOL_EVENT& aEvent )
1476 {
1477     ClearSelection( true /*quiet mode*/ );
1478     wxString sheetPath = *aEvent.Parameter<wxString*>();
1479 
1480     selectAllItemsOnSheet( sheetPath );
1481 
1482     zoomFitSelection();
1483 
1484     if( m_selection.Size() > 0 )
1485         m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
1486 
1487     return 0;
1488 }
1489 
1490 
selectSameSheet(const TOOL_EVENT & aEvent)1491 int PCB_SELECTION_TOOL::selectSameSheet( const TOOL_EVENT& aEvent )
1492 {
1493     if( !selectCursor( true ) )
1494         return 0;
1495 
1496     // this function currently only supports footprints since they are only on one sheet.
1497     auto item = m_selection.Front();
1498 
1499     if( !item )
1500         return 0;
1501 
1502     if( item->Type() != PCB_FOOTPRINT_T )
1503         return 0;
1504 
1505     FOOTPRINT* footprint = dynamic_cast<FOOTPRINT*>( item );
1506 
1507     if( footprint->GetPath().empty() )
1508         return 0;
1509 
1510     ClearSelection( true /*quiet mode*/ );
1511 
1512     // get the sheet path only.
1513     wxString sheetPath = footprint->GetPath().AsString().BeforeLast( '/' );
1514 
1515     if( sheetPath.IsEmpty() )
1516         sheetPath += '/';
1517 
1518     selectAllItemsOnSheet( sheetPath );
1519 
1520     // Inform other potentially interested tools
1521     if( m_selection.Size() > 0 )
1522         m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
1523 
1524     return 0;
1525 }
1526 
1527 
FindItem(BOARD_ITEM * aItem)1528 void PCB_SELECTION_TOOL::FindItem( BOARD_ITEM* aItem )
1529 {
1530     bool cleared = false;
1531 
1532     if( m_selection.GetSize() > 0 )
1533     {
1534         // Don't fire an event now; most of the time it will be redundant as we're about to
1535         // fire a SelectedEvent.
1536         cleared = true;
1537         ClearSelection( true /*quiet mode*/ );
1538     }
1539 
1540     if( aItem )
1541     {
1542         select( aItem );
1543         m_frame->FocusOnLocation( aItem->GetPosition() );
1544 
1545         // Inform other potentially interested tools
1546         m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
1547     }
1548     else if( cleared )
1549     {
1550         m_toolMgr->ProcessEvent( EVENTS::ClearedEvent );
1551     }
1552 
1553     m_frame->GetCanvas()->ForceRefresh();
1554 }
1555 
1556 
1557 /**
1558  * Determine if an item is included by the filter specified.
1559  *
1560  * @return true if aItem should be selected by this filter (i..e not filtered out)
1561  */
itemIsIncludedByFilter(const BOARD_ITEM & aItem,const BOARD & aBoard,const DIALOG_FILTER_SELECTION::OPTIONS & aFilterOptions)1562 static bool itemIsIncludedByFilter( const BOARD_ITEM& aItem, const BOARD& aBoard,
1563                                     const DIALOG_FILTER_SELECTION::OPTIONS& aFilterOptions )
1564 {
1565     bool include = true;
1566     const PCB_LAYER_ID layer = aItem.GetLayer();
1567 
1568     // if the item needs to be checked against the options
1569     if( include )
1570     {
1571         switch( aItem.Type() )
1572         {
1573         case PCB_FOOTPRINT_T:
1574         {
1575             const FOOTPRINT& footprint = static_cast<const FOOTPRINT&>( aItem );
1576 
1577             include = aFilterOptions.includeModules;
1578 
1579             if( include && !aFilterOptions.includeLockedModules )
1580                 include = !footprint.IsLocked();
1581 
1582             break;
1583         }
1584         case PCB_TRACE_T:
1585         case PCB_ARC_T:
1586             include = aFilterOptions.includeTracks;
1587             break;
1588 
1589         case PCB_VIA_T:
1590             include = aFilterOptions.includeVias;
1591             break;
1592 
1593         case PCB_FP_ZONE_T:
1594         case PCB_ZONE_T:
1595             include = aFilterOptions.includeZones;
1596             break;
1597 
1598         case PCB_SHAPE_T:
1599         case PCB_TARGET_T:
1600         case PCB_DIM_ALIGNED_T:
1601         case PCB_DIM_CENTER_T:
1602         case PCB_DIM_ORTHOGONAL_T:
1603         case PCB_DIM_LEADER_T:
1604             if( layer == Edge_Cuts )
1605                 include = aFilterOptions.includeBoardOutlineLayer;
1606             else
1607                 include = aFilterOptions.includeItemsOnTechLayers;
1608             break;
1609 
1610         case PCB_FP_TEXT_T:
1611         case PCB_TEXT_T:
1612             include = aFilterOptions.includePcbTexts;
1613             break;
1614 
1615         default:
1616             // no filtering, just select it
1617             break;
1618         }
1619     }
1620 
1621     return include;
1622 }
1623 
1624 
filterSelection(const TOOL_EVENT & aEvent)1625 int PCB_SELECTION_TOOL::filterSelection( const TOOL_EVENT& aEvent )
1626 {
1627     const BOARD&                      board = *getModel<BOARD>();
1628     DIALOG_FILTER_SELECTION::OPTIONS& opts = m_priv->m_filterOpts;
1629     DIALOG_FILTER_SELECTION           dlg( m_frame, opts );
1630 
1631     const int cmd = dlg.ShowModal();
1632 
1633     if( cmd != wxID_OK )
1634         return 0;
1635 
1636     // copy current selection
1637     std::deque<EDA_ITEM*> selection = m_selection.GetItems();
1638 
1639     ClearSelection( true /*quiet mode*/ );
1640 
1641     // re-select items from the saved selection according to the dialog options
1642     for( EDA_ITEM* i : selection )
1643     {
1644         BOARD_ITEM* item = static_cast<BOARD_ITEM*>( i );
1645         bool        include = itemIsIncludedByFilter( *item, board, opts );
1646 
1647         if( include )
1648             select( item );
1649     }
1650 
1651     m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
1652 
1653     return 0;
1654 }
1655 
1656 
FilterCollectedItems(GENERAL_COLLECTOR & aCollector,bool aMultiSelect)1657 void PCB_SELECTION_TOOL::FilterCollectedItems( GENERAL_COLLECTOR& aCollector, bool aMultiSelect )
1658 {
1659     if( aCollector.GetCount() == 0 )
1660         return;
1661 
1662     std::set<BOARD_ITEM*> rejected;
1663 
1664     for( EDA_ITEM* i : aCollector )
1665     {
1666         BOARD_ITEM* item = static_cast<BOARD_ITEM*>( i );
1667 
1668         if( !itemPassesFilter( item, aMultiSelect ) )
1669             rejected.insert( item );
1670     }
1671 
1672     for( BOARD_ITEM* item : rejected )
1673         aCollector.Remove( item );
1674 }
1675 
1676 
itemPassesFilter(BOARD_ITEM * aItem,bool aMultiSelect)1677 bool PCB_SELECTION_TOOL::itemPassesFilter( BOARD_ITEM* aItem, bool aMultiSelect )
1678 {
1679     if( !m_filter.lockedItems )
1680     {
1681         if( aItem->IsLocked() || ( aItem->GetParent() && aItem->GetParent()->IsLocked() ) )
1682         {
1683             if( aItem->Type() == PCB_PAD_T && !aMultiSelect )
1684             {
1685                 // allow a single pad to be selected -- there are a lot of operations that
1686                 // require this so we allow this one inconsistency
1687             }
1688             else
1689             {
1690                 return false;
1691             }
1692         }
1693     }
1694 
1695     switch( aItem->Type() )
1696     {
1697     case PCB_FOOTPRINT_T:
1698         if( !m_filter.footprints )
1699             return false;
1700 
1701         break;
1702 
1703     case PCB_PAD_T:
1704         if( !m_filter.pads )
1705             return false;
1706 
1707         break;
1708 
1709     case PCB_TRACE_T:
1710     case PCB_ARC_T:
1711         if( !m_filter.tracks )
1712             return false;
1713 
1714         break;
1715 
1716     case PCB_VIA_T:
1717         if( !m_filter.vias )
1718             return false;
1719 
1720         break;
1721 
1722     case PCB_FP_ZONE_T:
1723     case PCB_ZONE_T:
1724     {
1725         ZONE* zone = static_cast<ZONE*>( aItem );
1726 
1727         if( ( !m_filter.zones && !zone->GetIsRuleArea() )
1728             || ( !m_filter.keepouts && zone->GetIsRuleArea() ) )
1729         {
1730             return false;
1731         }
1732 
1733         break;
1734     }
1735 
1736     case PCB_FP_SHAPE_T:
1737     case PCB_SHAPE_T:
1738     case PCB_TARGET_T:
1739         if( !m_filter.graphics )
1740             return false;
1741 
1742         break;
1743 
1744     case PCB_FP_TEXT_T:
1745     case PCB_TEXT_T:
1746         if( !m_filter.text )
1747             return false;
1748 
1749         break;
1750 
1751     case PCB_DIM_ALIGNED_T:
1752     case PCB_DIM_CENTER_T:
1753     case PCB_DIM_ORTHOGONAL_T:
1754     case PCB_DIM_LEADER_T:
1755         if( !m_filter.dimensions )
1756             return false;
1757 
1758         break;
1759 
1760     default:
1761         if( !m_filter.otherItems )
1762             return false;
1763     }
1764 
1765     return true;
1766 }
1767 
1768 
ClearSelection(bool aQuietMode)1769 void PCB_SELECTION_TOOL::ClearSelection( bool aQuietMode )
1770 {
1771     if( m_selection.Empty() )
1772         return;
1773 
1774     while( m_selection.GetSize() )
1775         unhighlight( static_cast<BOARD_ITEM*>( m_selection.Front() ), SELECTED, &m_selection );
1776 
1777     view()->Update( &m_selection );
1778 
1779     m_selection.SetIsHover( false );
1780     m_selection.ClearReferencePoint();
1781 
1782     // Inform other potentially interested tools
1783     if( !aQuietMode )
1784     {
1785         m_toolMgr->ProcessEvent( EVENTS::ClearedEvent );
1786         m_toolMgr->RunAction( PCB_ACTIONS::hideDynamicRatsnest, true );
1787     }
1788 }
1789 
1790 
RebuildSelection()1791 void PCB_SELECTION_TOOL::RebuildSelection()
1792 {
1793     m_selection.Clear();
1794 
1795     bool enteredGroupFound = false;
1796 
1797     INSPECTOR_FUNC inspector =
1798             [&]( EDA_ITEM* item, void* testData )
1799             {
1800                 if( item->IsSelected() )
1801                 {
1802                     EDA_ITEM* parent = item->GetParent();
1803 
1804                     // Let selected parents handle their children.
1805                     if( parent && parent->IsSelected() )
1806                         return SEARCH_RESULT::CONTINUE;
1807 
1808                     highlight( (BOARD_ITEM*) item, SELECTED, &m_selection );
1809                 }
1810 
1811                 if( item == m_enteredGroup )
1812                 {
1813                     item->SetFlags( ENTERED );
1814                     enteredGroupFound = true;
1815                 }
1816                 else
1817                 {
1818                     item->ClearFlags( ENTERED );
1819                 }
1820 
1821                 return SEARCH_RESULT::CONTINUE;
1822             };
1823 
1824     board()->Visit( inspector, nullptr, m_isFootprintEditor ? GENERAL_COLLECTOR::FootprintItems
1825                                                             : GENERAL_COLLECTOR::AllBoardItems );
1826 
1827     if( !enteredGroupFound )
1828     {
1829         m_enteredGroupOverlay.Clear();
1830         m_enteredGroup = nullptr;
1831     }
1832 }
1833 
1834 
SelectionMenu(const TOOL_EVENT & aEvent)1835 int PCB_SELECTION_TOOL::SelectionMenu( const TOOL_EVENT& aEvent )
1836 {
1837     GENERAL_COLLECTOR* collector = aEvent.Parameter<GENERAL_COLLECTOR*>();
1838 
1839     doSelectionMenu( collector );
1840 
1841     return 0;
1842 }
1843 
1844 
doSelectionMenu(GENERAL_COLLECTOR * aCollector)1845 bool PCB_SELECTION_TOOL::doSelectionMenu( GENERAL_COLLECTOR* aCollector )
1846 {
1847     BOARD_ITEM*   current = nullptr;
1848     PCB_SELECTION highlightGroup;
1849     bool          selectAll = false;
1850     bool          expandSelection = false;
1851 
1852     highlightGroup.SetLayer( LAYER_SELECT_OVERLAY );
1853     getView()->Add( &highlightGroup );
1854 
1855     do
1856     {
1857         /// The user has requested the full, non-limited list of selection items
1858         if( expandSelection )
1859             aCollector->Combine();
1860 
1861         expandSelection = false;
1862 
1863         int         limit = std::min( 9, aCollector->GetCount() );
1864         ACTION_MENU menu( true );
1865 
1866         for( int i = 0; i < limit; ++i )
1867         {
1868             wxString    text;
1869             BOARD_ITEM* item = ( *aCollector )[i];
1870             text             = item->GetSelectMenuText( m_frame->GetUserUnits() );
1871 
1872             wxString menuText = wxString::Format( "&%d. %s\t%d", i + 1, text, i + 1 );
1873             menu.Add( menuText, i + 1, item->GetMenuImage() );
1874         }
1875 
1876         menu.AppendSeparator();
1877         menu.Add( _( "Select &All\tA" ), limit + 1, BITMAPS::INVALID_BITMAP );
1878 
1879         if( !expandSelection && aCollector->HasAdditionalItems() )
1880             menu.Add( _( "&Expand Selection\tE" ), limit + 2, BITMAPS::INVALID_BITMAP );
1881 
1882         if( aCollector->m_MenuTitle.Length() )
1883         {
1884             menu.SetTitle( aCollector->m_MenuTitle );
1885             menu.SetIcon( BITMAPS::info );
1886             menu.DisplayTitle( true );
1887         }
1888         else
1889         {
1890             menu.DisplayTitle( false );
1891         }
1892 
1893         SetContextMenu( &menu, CMENU_NOW );
1894 
1895         while( TOOL_EVENT* evt = Wait() )
1896         {
1897             if( evt->Action() == TA_CHOICE_MENU_UPDATE )
1898             {
1899                 if( selectAll )
1900                 {
1901                     for( int i = 0; i < aCollector->GetCount(); ++i )
1902                         unhighlight( ( *aCollector )[i], BRIGHTENED, &highlightGroup );
1903                 }
1904                 else if( current )
1905                 {
1906                     unhighlight( current, BRIGHTENED, &highlightGroup );
1907                 }
1908 
1909                 int id = *evt->GetCommandId();
1910 
1911                 // User has pointed an item, so show it in a different way
1912                 if( id > 0 && id <= limit )
1913                 {
1914                     current = ( *aCollector )[id - 1];
1915                     highlight( current, BRIGHTENED, &highlightGroup );
1916                 }
1917                 else
1918                 {
1919                     current = nullptr;
1920                 }
1921 
1922                 // User has pointed on the "Select All" option
1923                 if( id == limit + 1 )
1924                 {
1925                     for( int i = 0; i < aCollector->GetCount(); ++i )
1926                         highlight( ( *aCollector )[i], BRIGHTENED, &highlightGroup );
1927                     selectAll = true;
1928                 }
1929                 else
1930                 {
1931                     selectAll = false;
1932                 }
1933             }
1934             else if( evt->Action() == TA_CHOICE_MENU_CHOICE )
1935             {
1936                 if( selectAll )
1937                 {
1938                     for( int i = 0; i < aCollector->GetCount(); ++i )
1939                         unhighlight( ( *aCollector )[i], BRIGHTENED, &highlightGroup );
1940                 }
1941                 else if( current )
1942                 {
1943                     unhighlight( current, BRIGHTENED, &highlightGroup );
1944                 }
1945 
1946                 OPT<int> id = evt->GetCommandId();
1947 
1948                 // User has selected the "Select All" option
1949                 if( id == limit + 1 )
1950                 {
1951                     selectAll = true;
1952                     current   = nullptr;
1953                 }
1954                 else if( id == limit + 2 )
1955                 {
1956                     expandSelection = true;
1957                     selectAll       = false;
1958                     current         = nullptr;
1959                 }
1960                 // User has selected an item, so this one will be returned
1961                 else if( id && ( *id > 0 ) && ( *id <= limit ) )
1962                 {
1963                     selectAll = false;
1964                     current   = ( *aCollector )[*id - 1];
1965                 }
1966                 else
1967                 {
1968                     selectAll = false;
1969                     current   = nullptr;
1970                 }
1971             }
1972             else if( evt->Action() == TA_CHOICE_MENU_CLOSED )
1973             {
1974                 break;
1975             }
1976         }
1977     } while( expandSelection );
1978 
1979     getView()->Remove( &highlightGroup );
1980 
1981     if( selectAll )
1982     {
1983         return true;
1984     }
1985     else if( current )
1986     {
1987         aCollector->Empty();
1988         aCollector->Append( current );
1989         return true;
1990     }
1991 
1992     return false;
1993 }
1994 
1995 
Selectable(const BOARD_ITEM * aItem,bool checkVisibilityOnly) const1996 bool PCB_SELECTION_TOOL::Selectable( const BOARD_ITEM* aItem, bool checkVisibilityOnly ) const
1997 {
1998     const RENDER_SETTINGS* settings = getView()->GetPainter()->GetSettings();
1999 
2000     if( settings->GetHighContrast() )
2001     {
2002         std::set<unsigned int> activeLayers = settings->GetHighContrastLayers();
2003         bool                   onActiveLayer = false;
2004 
2005         for( unsigned int layer : activeLayers )
2006         {
2007             // NOTE: Only checking the regular layers (not GAL meta-layers)
2008             if( layer < PCB_LAYER_ID_COUNT && aItem->IsOnLayer( ToLAYER_ID( layer ) ) )
2009             {
2010                 onActiveLayer = true;
2011                 break;
2012             }
2013         }
2014 
2015         if( !onActiveLayer ) // We do not want to select items that are in the background
2016             return false;
2017     }
2018 
2019     if( aItem->Type() == PCB_FOOTPRINT_T )
2020     {
2021         // In footprint editor, we do not want to select the footprint itself.
2022         if( m_isFootprintEditor )
2023             return false;
2024 
2025         // Allow selection of footprints if some part of the footprint is visible.
2026         const FOOTPRINT* footprint = static_cast<const FOOTPRINT*>( aItem );
2027 
2028         // If the footprint has no items except the reference and value fields, include the
2029         // footprint in the selections.
2030         if( footprint->GraphicalItems().empty()
2031           && footprint->Pads().empty()
2032           && footprint->Zones().empty() )
2033             return true;
2034 
2035         for( const BOARD_ITEM* item : footprint->GraphicalItems() )
2036         {
2037             if( Selectable( item, true ) )
2038                 return true;
2039         }
2040 
2041         for( const PAD* pad : footprint->Pads() )
2042         {
2043             if( Selectable( pad, true ) )
2044                 return true;
2045         }
2046 
2047         for( const ZONE* zone : footprint->Zones() )
2048         {
2049             if( Selectable( zone, true ) )
2050                 return true;
2051         }
2052 
2053         return false;
2054     }
2055     else if( aItem->Type() == PCB_GROUP_T )
2056     {
2057         PCB_GROUP* group = const_cast<PCB_GROUP*>( static_cast<const PCB_GROUP*>( aItem ) );
2058 
2059         // Similar to logic for footprint, a group is selectable if any of its members are.
2060         // (This recurses.)
2061         for( BOARD_ITEM* item : group->GetItems() )
2062         {
2063             if( Selectable( item, true ) )
2064                 return true;
2065         }
2066 
2067         return false;
2068     }
2069 
2070     const ZONE*    zone = nullptr;
2071     const PCB_VIA* via = nullptr;
2072     const PAD*     pad = nullptr;
2073 
2074     switch( aItem->Type() )
2075     {
2076     case PCB_ZONE_T:
2077     case PCB_FP_ZONE_T:
2078         if( !board()->IsElementVisible( LAYER_ZONES ) )
2079             return false;
2080 
2081         zone = static_cast<const ZONE*>( aItem );
2082 
2083         // A footprint zone is only selectable within the footprint editor
2084         if( zone->GetParent()
2085                 && zone->GetParent()->Type() == PCB_FOOTPRINT_T
2086                 && !m_isFootprintEditor
2087                 && !checkVisibilityOnly )
2088         {
2089             return false;
2090         }
2091 
2092         // zones can exist on multiple layers!
2093         if( !( zone->GetLayerSet() & board()->GetVisibleLayers() ).any() )
2094             return false;
2095 
2096         break;
2097 
2098     case PCB_TRACE_T:
2099     case PCB_ARC_T:
2100         if( !board()->IsElementVisible( LAYER_TRACKS ) )
2101             return false;
2102 
2103         if( m_isFootprintEditor )
2104         {
2105             if( !view()->IsLayerVisible( aItem->GetLayer() ) )
2106                 return false;
2107         }
2108         else
2109         {
2110             if( !board()->IsLayerVisible( aItem->GetLayer() ) )
2111                 return false;
2112         }
2113 
2114         break;
2115 
2116     case PCB_VIA_T:
2117         if( !board()->IsElementVisible( LAYER_VIAS ) )
2118             return false;
2119 
2120         via = static_cast<const PCB_VIA*>( aItem );
2121 
2122         // For vias it is enough if only one of its layers is visible
2123         if( !( board()->GetVisibleLayers() & via->GetLayerSet() ).any() )
2124             return false;
2125 
2126         break;
2127 
2128     case PCB_FP_TEXT_T:
2129         if( m_isFootprintEditor )
2130         {
2131             if( !view()->IsLayerVisible( aItem->GetLayer() ) )
2132                 return false;
2133         }
2134         else
2135         {
2136             if( !view()->IsVisible( aItem ) )
2137                 return false;
2138 
2139             if( !board()->IsLayerVisible( aItem->GetLayer() ) )
2140                 return false;
2141         }
2142 
2143         break;
2144 
2145     case PCB_FP_SHAPE_T:
2146         if( m_isFootprintEditor )
2147         {
2148             if( !view()->IsLayerVisible( aItem->GetLayer() ) )
2149                 return false;
2150         }
2151         else
2152         {
2153             // Footprint shape selections are only allowed in footprint editor mode.
2154             if( !checkVisibilityOnly )
2155                 return false;
2156 
2157             if( !board()->IsLayerVisible( aItem->GetLayer() ) )
2158                 return false;
2159         }
2160 
2161         break;
2162 
2163     case PCB_PAD_T:
2164         // Multiple selection is only allowed in footprint editor mode.  In pcbnew, you have to
2165         // select footprint subparts one by one, rather than with a drag selection.  This is so
2166         // you can pick up items under an (unlocked) footprint without also moving the
2167         // footprint's sub-parts.
2168         if( !m_isFootprintEditor && !checkVisibilityOnly )
2169         {
2170             if( m_multiple )
2171                 return false;
2172         }
2173 
2174         pad = static_cast<const PAD*>( aItem );
2175 
2176         if( pad->GetAttribute() == PAD_ATTRIB::PTH || pad->GetAttribute() == PAD_ATTRIB::NPTH )
2177         {
2178             // Check render mode (from the Items tab) first
2179             if( !board()->IsElementVisible( LAYER_PADS_TH ) )
2180                 return false;
2181 
2182             // A pad's hole is visible on every layer the pad is visible on plus many layers the
2183             // pad is not visible on -- so we only need to check for any visible hole layers.
2184             if( !( board()->GetVisibleLayers() & LSET::PhysicalLayersMask() ).any() )
2185                 return false;
2186         }
2187         else
2188         {
2189             // Check render mode (from the Items tab) first
2190             if( pad->IsOnLayer( F_Cu ) && !board()->IsElementVisible( LAYER_PAD_FR ) )
2191                 return false;
2192             else if( pad->IsOnLayer( B_Cu ) && !board()->IsElementVisible( LAYER_PAD_BK ) )
2193                 return false;
2194 
2195             if( !( pad->GetLayerSet() & board()->GetVisibleLayers() ).any() )
2196                 return false;
2197         }
2198 
2199         break;
2200 
2201     // These are not selectable
2202     case PCB_NETINFO_T:
2203     case NOT_USED:
2204     case TYPE_NOT_INIT:
2205         return false;
2206 
2207     default:    // Suppress warnings
2208         break;
2209     }
2210 
2211     return aItem->ViewGetLOD( aItem->GetLayer(), view() ) < view()->GetScale();
2212 }
2213 
2214 
select(BOARD_ITEM * aItem)2215 void PCB_SELECTION_TOOL::select( BOARD_ITEM* aItem )
2216 {
2217     if( aItem->IsSelected() )
2218         return;
2219 
2220     if( aItem->Type() == PCB_PAD_T )
2221     {
2222         FOOTPRINT* footprint = static_cast<FOOTPRINT*>( aItem->GetParent() );
2223 
2224         if( m_selection.Contains( footprint ) )
2225             return;
2226     }
2227 
2228     highlight( aItem, SELECTED, &m_selection );
2229 }
2230 
2231 
unselect(BOARD_ITEM * aItem)2232 void PCB_SELECTION_TOOL::unselect( BOARD_ITEM* aItem )
2233 {
2234     unhighlight( aItem, SELECTED, &m_selection );
2235 }
2236 
2237 
highlight(BOARD_ITEM * aItem,int aMode,PCB_SELECTION * aGroup)2238 void PCB_SELECTION_TOOL::highlight( BOARD_ITEM* aItem, int aMode, PCB_SELECTION* aGroup )
2239 {
2240     if( aGroup )
2241         aGroup->Add( aItem );
2242 
2243     highlightInternal( aItem, aMode, aGroup != nullptr );
2244     view()->Update( aItem, KIGFX::REPAINT );
2245 
2246     // Many selections are very temporal and updating the display each time just
2247     // creates noise.
2248     if( aMode == BRIGHTENED )
2249         getView()->MarkTargetDirty( KIGFX::TARGET_OVERLAY );
2250 }
2251 
2252 
highlightInternal(BOARD_ITEM * aItem,int aMode,bool aUsingOverlay)2253 void PCB_SELECTION_TOOL::highlightInternal( BOARD_ITEM* aItem, int aMode, bool aUsingOverlay )
2254 {
2255     if( aMode == SELECTED )
2256         aItem->SetSelected();
2257     else if( aMode == BRIGHTENED )
2258         aItem->SetBrightened();
2259 
2260     if( aUsingOverlay )
2261         view()->Hide( aItem, true );    // Hide the original item, so it is shown only on overlay
2262 
2263     if( aItem->Type() == PCB_FOOTPRINT_T )
2264     {
2265         static_cast<FOOTPRINT*>( aItem )->RunOnChildren(
2266                 [&]( BOARD_ITEM* aChild )
2267                 {
2268                     highlightInternal( aChild, aMode, aUsingOverlay );
2269                 } );
2270     }
2271     else if( aItem->Type() == PCB_GROUP_T )
2272     {
2273         static_cast<PCB_GROUP*>( aItem )->RunOnChildren(
2274                 [&]( BOARD_ITEM* aChild )
2275                 {
2276                     highlightInternal( aChild, aMode, aUsingOverlay );
2277                 } );
2278     }
2279 }
2280 
2281 
unhighlight(BOARD_ITEM * aItem,int aMode,PCB_SELECTION * aGroup)2282 void PCB_SELECTION_TOOL::unhighlight( BOARD_ITEM* aItem, int aMode, PCB_SELECTION* aGroup )
2283 {
2284     if( aGroup )
2285         aGroup->Remove( aItem );
2286 
2287     unhighlightInternal( aItem, aMode, aGroup != nullptr );
2288     view()->Update( aItem, KIGFX::REPAINT );
2289 
2290     // Many selections are very temporal and updating the display each time just creates noise.
2291     if( aMode == BRIGHTENED )
2292         getView()->MarkTargetDirty( KIGFX::TARGET_OVERLAY );
2293 }
2294 
2295 
unhighlightInternal(BOARD_ITEM * aItem,int aMode,bool aUsingOverlay)2296 void PCB_SELECTION_TOOL::unhighlightInternal( BOARD_ITEM* aItem, int aMode, bool aUsingOverlay )
2297 {
2298     if( aMode == SELECTED )
2299         aItem->ClearSelected();
2300     else if( aMode == BRIGHTENED )
2301         aItem->ClearBrightened();
2302 
2303     if( aUsingOverlay )
2304         view()->Hide( aItem, false );   // // Restore original item visibility
2305 
2306     if( aItem->Type() == PCB_FOOTPRINT_T )
2307     {
2308         static_cast<FOOTPRINT*>( aItem )->RunOnChildren(
2309                 [&]( BOARD_ITEM* aChild )
2310                 {
2311                     unhighlightInternal( aChild, aMode, aUsingOverlay );
2312                 } );
2313     }
2314     else if( aItem->Type() == PCB_GROUP_T )
2315     {
2316         static_cast<PCB_GROUP*>( aItem )->RunOnChildren(
2317                 [&]( BOARD_ITEM* aChild )
2318                 {
2319                     unhighlightInternal( aChild, aMode, aUsingOverlay );
2320                 } );
2321     }
2322 }
2323 
2324 
selectionContains(const VECTOR2I & aPoint) const2325 bool PCB_SELECTION_TOOL::selectionContains( const VECTOR2I& aPoint ) const
2326 {
2327     GENERAL_COLLECTORS_GUIDE   guide = getCollectorsGuide();
2328     GENERAL_COLLECTOR          collector;
2329 
2330     // Since we're just double-checking, we want a considerably sloppier check than the initial
2331     // selection (for which most tools use 5 pixels).  So we increase this to an effective 20
2332     // pixels by artificially inflating the value of a pixel by 4X.
2333     guide.SetOnePixelInIU( guide.OnePixelInIU() * 4 );
2334 
2335     collector.Collect( board(), m_isFootprintEditor ? GENERAL_COLLECTOR::FootprintItems
2336                                                     : GENERAL_COLLECTOR::AllBoardItems,
2337                        (wxPoint) aPoint, guide );
2338 
2339     for( int i = collector.GetCount() - 1; i >= 0; --i )
2340     {
2341         BOARD_ITEM* item = collector[i];
2342 
2343         if( item->IsSelected() && item->HitTest( (wxPoint) aPoint, 5 * guide.OnePixelInIU() ) )
2344             return true;
2345     }
2346 
2347     return false;
2348 }
2349 
2350 
hitTestDistance(const wxPoint & aWhere,BOARD_ITEM * aItem,int aMaxDistance) const2351 int PCB_SELECTION_TOOL::hitTestDistance( const wxPoint& aWhere, BOARD_ITEM* aItem,
2352                                          int aMaxDistance ) const
2353 {
2354     BOX2D viewportD = getView()->GetViewport();
2355     BOX2I viewport( VECTOR2I( viewportD.GetPosition() ), VECTOR2I( viewportD.GetSize() ) );
2356     int   distance = INT_MAX;
2357     SEG   loc( aWhere, aWhere );
2358 
2359     switch( aItem->Type() )
2360     {
2361     case PCB_TEXT_T:
2362     {
2363         PCB_TEXT* text = static_cast<PCB_TEXT*>( aItem );
2364         text->GetEffectiveTextShape()->Collide( loc, aMaxDistance, &distance );
2365         break;
2366     }
2367 
2368     case PCB_FP_TEXT_T:
2369     {
2370         FP_TEXT* text = static_cast<FP_TEXT*>( aItem );
2371         text->GetEffectiveTextShape()->Collide( loc, aMaxDistance, &distance );
2372         break;
2373     }
2374 
2375     case PCB_ZONE_T:
2376     {
2377         ZONE* zone = static_cast<ZONE*>( aItem );
2378 
2379         // Zone borders are very specific
2380         if( zone->HitTestForEdge( aWhere, aMaxDistance / 2 ) )
2381             distance = 0;
2382         else if( zone->HitTestForEdge( aWhere, aMaxDistance ) )
2383             distance = aMaxDistance / 2;
2384         else
2385             aItem->GetEffectiveShape()->Collide( loc, aMaxDistance, &distance );
2386 
2387         break;
2388     }
2389 
2390     case PCB_FOOTPRINT_T:
2391     {
2392         FOOTPRINT* footprint = static_cast<FOOTPRINT*>( aItem );
2393         EDA_RECT   bbox = footprint->GetBoundingBox( false, false );
2394 
2395         try
2396         {
2397             footprint->GetBoundingHull().Collide( loc, aMaxDistance, &distance );
2398         }
2399         catch( const ClipperLib::clipperException& exc )
2400         {
2401             // This may be overkill and could be an assertion but we are more likely to find
2402             // any clipper errors this way.
2403             wxLogError( wxT( "Clipper library exception '%s' occurred." ), exc.what() );
2404         }
2405 
2406         // Consider footprints larger than the viewport only as a last resort
2407         if( bbox.GetHeight() > viewport.GetHeight() || bbox.GetWidth() > viewport.GetWidth() )
2408             distance = INT_MAX / 2;
2409 
2410         break;
2411     }
2412 
2413     case PCB_MARKER_T:
2414     {
2415         PCB_MARKER*      marker = static_cast<PCB_MARKER*>( aItem );
2416         SHAPE_LINE_CHAIN polygon;
2417 
2418         marker->ShapeToPolygon( polygon );
2419         polygon.Move( marker->GetPos() );
2420         polygon.Collide( loc, aMaxDistance, &distance );
2421         break;
2422     }
2423 
2424     case PCB_GROUP_T:
2425     {
2426         PCB_GROUP* group = static_cast<PCB_GROUP*>( aItem );
2427 
2428         for( BOARD_ITEM* member : group->GetItems() )
2429             distance = std::min( distance, hitTestDistance( aWhere, member, aMaxDistance ) );
2430 
2431         break;
2432     }
2433 
2434     default:
2435         aItem->GetEffectiveShape()->Collide( loc, aMaxDistance, &distance );
2436         break;
2437     }
2438 
2439     return distance;
2440 }
2441 
2442 
2443 // The general idea here is that if the user clicks directly on a small item inside a larger
2444 // one, then they want the small item.  The quintessential case of this is clicking on a pad
2445 // within a footprint, but we also apply it for text within a footprint, footprints within
2446 // larger footprints, and vias within either larger pads or longer tracks.
2447 //
2448 // These "guesses" presume there is area within the larger item to click in to select it.  If
2449 // an item is mostly covered by smaller items within it, then the guesses are inappropriate as
2450 // there might not be any area left to click to select the larger item.  In this case we must
2451 // leave the items in the collector and bring up a Selection Clarification menu.
2452 //
2453 // We currently check for pads and text mostly covering a footprint, but we don't check for
2454 // smaller footprints mostly covering a larger footprint.
2455 //
GuessSelectionCandidates(GENERAL_COLLECTOR & aCollector,const VECTOR2I & aWhere) const2456 void PCB_SELECTION_TOOL::GuessSelectionCandidates( GENERAL_COLLECTOR& aCollector,
2457                                                    const VECTOR2I& aWhere ) const
2458 {
2459     std::set<BOARD_ITEM*> preferred;
2460     std::set<BOARD_ITEM*> rejected;
2461     wxPoint               where( aWhere.x, aWhere.y );
2462 
2463     PCB_LAYER_ID activeLayer = m_frame->GetActiveLayer();
2464     LSET         silkLayers( 2, B_SilkS, F_SilkS );
2465 
2466     if( silkLayers[activeLayer] )
2467     {
2468         for( int i = 0; i < aCollector.GetCount(); ++i )
2469         {
2470             BOARD_ITEM* item = aCollector[i];
2471             KICAD_T type = item->Type();
2472 
2473             if( ( type == PCB_FP_TEXT_T || type == PCB_TEXT_T || type == PCB_SHAPE_T )
2474                     && silkLayers[item->GetLayer()] )
2475             {
2476                 preferred.insert( item );
2477             }
2478         }
2479 
2480         if( preferred.size() > 0 )
2481         {
2482             aCollector.Empty();
2483 
2484             for( BOARD_ITEM* item : preferred )
2485                 aCollector.Append( item );
2486 
2487             return;
2488         }
2489     }
2490 
2491     // Prefer exact hits to sloppy ones
2492     constexpr int MAX_SLOP = 5;
2493 
2494     int pixel = (int) aCollector.GetGuide()->OnePixelInIU();
2495     int minSlop = INT_MAX;
2496 
2497     std::map<BOARD_ITEM*, int> itemsBySloppiness;
2498 
2499     for( int i = 0; i < aCollector.GetCount(); ++i )
2500     {
2501         BOARD_ITEM* item = aCollector[i];
2502         int         itemSlop = hitTestDistance( where, item, MAX_SLOP * pixel );
2503 
2504         itemsBySloppiness[ item ] = itemSlop;
2505 
2506         if( itemSlop < minSlop )
2507             minSlop = itemSlop;
2508     }
2509 
2510     // Prune sloppier items
2511     if( minSlop < INT_MAX )
2512     {
2513         for( std::pair<BOARD_ITEM*, int> pair : itemsBySloppiness )
2514         {
2515             if( pair.second > minSlop + pixel )
2516                 aCollector.Transfer( pair.first );
2517         }
2518     }
2519 
2520     // If the user clicked on a small item within a much larger one then it's pretty clear
2521     // they're trying to select the smaller one.
2522     constexpr double sizeRatio = 1.5;
2523 
2524     std::vector<std::pair<BOARD_ITEM*, double>> itemsByArea;
2525 
2526     for( int i = 0; i < aCollector.GetCount(); ++i )
2527     {
2528         BOARD_ITEM* item = aCollector[i];
2529         double      area;
2530 
2531         if( ( item->Type() == PCB_ZONE_T || item->Type() == PCB_FP_ZONE_T )
2532                 && static_cast<ZONE*>( item )->HitTestForEdge( where, MAX_SLOP * pixel / 2 ) )
2533         {
2534             // Zone borders are very specific, so make them "small"
2535             area = MAX_SLOP * SEG::Square( pixel );
2536         }
2537         else if( item->Type() == PCB_VIA_T )
2538         {
2539             // Vias rarely hide other things, and we don't want them deferring to short track
2540             // segments underneath them -- so artificially reduce their size from πr² to 1.5r².
2541             area = SEG::Square( static_cast<PCB_VIA*>( item )->GetDrill() / 2 ) * 1.5;
2542         }
2543         else
2544         {
2545             try
2546             {
2547                 area = FOOTPRINT::GetCoverageArea( item, aCollector );
2548             }
2549             catch( const ClipperLib::clipperException& e )
2550             {
2551                 wxLogError( "A clipper exception %s was detected.", e.what() );
2552             }
2553         }
2554 
2555         itemsByArea.emplace_back( item, area );
2556     }
2557 
2558     std::sort( itemsByArea.begin(), itemsByArea.end(),
2559                []( const std::pair<BOARD_ITEM*, double>& lhs,
2560                    const std::pair<BOARD_ITEM*, double>& rhs ) -> bool
2561                {
2562                    return lhs.second < rhs.second;
2563                } );
2564 
2565     bool rejecting = false;
2566 
2567     for( int i = 1; i < (int) itemsByArea.size(); ++i )
2568     {
2569         if( itemsByArea[i].second > itemsByArea[i-1].second * sizeRatio )
2570             rejecting = true;
2571 
2572         if( rejecting )
2573             rejected.insert( itemsByArea[i].first );
2574     }
2575 
2576     // Special case: if a footprint is completely covered with other features then there's no
2577     // way to select it -- so we need to leave it in the list for user disambiguation.
2578     constexpr double maxCoverRatio = 0.70;
2579 
2580     for( int i = 0; i < aCollector.GetCount(); ++i )
2581     {
2582         if( FOOTPRINT* footprint = dynamic_cast<FOOTPRINT*>( aCollector[i] ) )
2583         {
2584             if( footprint->CoverageRatio( aCollector ) > maxCoverRatio )
2585                 rejected.erase( footprint );
2586         }
2587     }
2588 
2589     // Hopefully we've now got what the user wanted.
2590     if( (unsigned) aCollector.GetCount() > rejected.size() )  // do not remove everything
2591     {
2592         for( BOARD_ITEM* item : rejected )
2593             aCollector.Transfer( item );
2594     }
2595 
2596     // Finally, what we are left with is a set of items of similar coverage area.  We now reject
2597     // any that are not on the active layer, to reduce the number of disambiguation menus shown.
2598     // If the user wants to force-disambiguate, they can either switch layers or use the modifier
2599     // key to force the menu.
2600     if( aCollector.GetCount() > 1 )
2601     {
2602         bool haveItemOnActive = false;
2603         rejected.clear();
2604 
2605         for( int i = 0; i < aCollector.GetCount(); ++i )
2606         {
2607             if( !aCollector[i]->IsOnLayer( activeLayer ) )
2608                 rejected.insert( aCollector[i] );
2609             else
2610                 haveItemOnActive = true;
2611         }
2612 
2613         if( haveItemOnActive )
2614             for( BOARD_ITEM* item : rejected )
2615                 aCollector.Transfer( item );
2616     }
2617 }
2618 
2619 
FilterCollectorForHierarchy(GENERAL_COLLECTOR & aCollector,bool aMultiselect) const2620 void PCB_SELECTION_TOOL::FilterCollectorForHierarchy( GENERAL_COLLECTOR& aCollector,
2621                                                       bool aMultiselect ) const
2622 {
2623     std::unordered_set<BOARD_ITEM*> toAdd;
2624 
2625     // Set TEMP_SELECTED on all parents which are included in the GENERAL_COLLECTOR.  This
2626     // algorithm is O3n, whereas checking for the parent inclusion could potentially be On^2.
2627     for( int j = 0; j < aCollector.GetCount(); j++ )
2628     {
2629         if( aCollector[j]->GetParent() )
2630             aCollector[j]->GetParent()->ClearFlags( TEMP_SELECTED );
2631     }
2632 
2633     if( aMultiselect )
2634     {
2635         for( int j = 0; j < aCollector.GetCount(); j++ )
2636             aCollector[j]->SetFlags( TEMP_SELECTED );
2637     }
2638 
2639     for( int j = 0; j < aCollector.GetCount(); )
2640     {
2641         BOARD_ITEM* item = aCollector[j];
2642         BOARD_ITEM* parent = item->GetParent();
2643         BOARD_ITEM* start = item;
2644 
2645         if( !m_isFootprintEditor && parent && parent->Type() == PCB_FOOTPRINT_T )
2646             start = parent;
2647 
2648         // If any element is a member of a group, replace those elements with the top containing
2649         // group.
2650         PCB_GROUP*  aTop = PCB_GROUP::TopLevelGroup( start, m_enteredGroup, m_isFootprintEditor );
2651 
2652         if( aTop )
2653         {
2654             if( aTop != item )
2655             {
2656                 toAdd.insert( aTop );
2657                 aTop->SetFlags( TEMP_SELECTED );
2658 
2659                 aCollector.Remove( item );
2660                 continue;
2661             }
2662         }
2663         else if( m_enteredGroup
2664                     && !PCB_GROUP::WithinScope( item, m_enteredGroup, m_isFootprintEditor ) )
2665         {
2666             // If a group is entered, disallow selections of objects outside the group.
2667             aCollector.Remove( item );
2668             continue;
2669         }
2670 
2671         // Footprints are a bit easier as they can't be nested.
2672         if( parent && ( parent->GetFlags() & TEMP_SELECTED ) )
2673         {
2674             // Remove children of selected items
2675             aCollector.Remove( item );
2676             continue;
2677         }
2678 
2679         ++j;
2680     }
2681 
2682     for( BOARD_ITEM* item : toAdd )
2683     {
2684         if( !aCollector.HasItem( item ) )
2685             aCollector.Append( item );
2686     }
2687 }
2688 
2689 
FilterCollectorForFreePads(GENERAL_COLLECTOR & aCollector) const2690 void PCB_SELECTION_TOOL::FilterCollectorForFreePads( GENERAL_COLLECTOR& aCollector ) const
2691 {
2692     std::set<BOARD_ITEM*> to_add;
2693 
2694     // Iterate from the back so we don't have to worry about removals.
2695     for( int i = aCollector.GetCount() - 1; i >= 0; --i )
2696     {
2697         BOARD_ITEM* item = aCollector[i];
2698 
2699         if( !IsFootprintEditor() && item->Type() == PCB_PAD_T
2700             && !frame()->Settings().m_AllowFreePads )
2701         {
2702             if( !aCollector.HasItem( item->GetParent() ) )
2703                 to_add.insert( item->GetParent() );
2704 
2705             aCollector.Remove( item );
2706         }
2707     }
2708 
2709     for( BOARD_ITEM* item : to_add )
2710         aCollector.Append( item );
2711 }
2712 
2713 
FilterCollectorForMarkers(GENERAL_COLLECTOR & aCollector) const2714 void PCB_SELECTION_TOOL::FilterCollectorForMarkers( GENERAL_COLLECTOR& aCollector ) const
2715 {
2716     // Iterate from the back so we don't have to worry about removals.
2717     for( int i = aCollector.GetCount() - 1; i >= 0; --i )
2718     {
2719         BOARD_ITEM* item = aCollector[i];
2720 
2721         if( item->Type() == PCB_MARKER_T )
2722             aCollector.Remove( item );
2723     }
2724 }
2725 
2726 
updateSelection(const TOOL_EVENT & aEvent)2727 int PCB_SELECTION_TOOL::updateSelection( const TOOL_EVENT& aEvent )
2728 {
2729     getView()->Update( &m_selection );
2730     getView()->Update( &m_enteredGroupOverlay );
2731 
2732     return 0;
2733 }
2734 
2735 
UpdateMenu(const TOOL_EVENT & aEvent)2736 int PCB_SELECTION_TOOL::UpdateMenu( const TOOL_EVENT& aEvent )
2737 {
2738     ACTION_MENU*      actionMenu = aEvent.Parameter<ACTION_MENU*>();
2739     CONDITIONAL_MENU* conditionalMenu = dynamic_cast<CONDITIONAL_MENU*>( actionMenu );
2740 
2741     if( conditionalMenu )
2742         conditionalMenu->Evaluate( m_selection );
2743 
2744     if( actionMenu )
2745         actionMenu->UpdateAll();
2746 
2747     return 0;
2748 }
2749 
2750 
setTransitions()2751 void PCB_SELECTION_TOOL::setTransitions()
2752 {
2753     Go( &PCB_SELECTION_TOOL::UpdateMenu,          ACTIONS::updateMenu.MakeEvent() );
2754 
2755     Go( &PCB_SELECTION_TOOL::Main,                PCB_ACTIONS::selectionActivate.MakeEvent() );
2756     Go( &PCB_SELECTION_TOOL::CursorSelection,     PCB_ACTIONS::selectionCursor.MakeEvent() );
2757     Go( &PCB_SELECTION_TOOL::ClearSelection,      PCB_ACTIONS::selectionClear.MakeEvent() );
2758 
2759     Go( &PCB_SELECTION_TOOL::SelectItem,          PCB_ACTIONS::selectItem.MakeEvent() );
2760     Go( &PCB_SELECTION_TOOL::SelectItems,         PCB_ACTIONS::selectItems.MakeEvent() );
2761     Go( &PCB_SELECTION_TOOL::UnselectItem,        PCB_ACTIONS::unselectItem.MakeEvent() );
2762     Go( &PCB_SELECTION_TOOL::UnselectItems,       PCB_ACTIONS::unselectItems.MakeEvent() );
2763     Go( &PCB_SELECTION_TOOL::SelectionMenu,       PCB_ACTIONS::selectionMenu.MakeEvent() );
2764 
2765     Go( &PCB_SELECTION_TOOL::filterSelection,     PCB_ACTIONS::filterSelection.MakeEvent() );
2766     Go( &PCB_SELECTION_TOOL::expandConnection,    PCB_ACTIONS::selectConnection.MakeEvent() );
2767     Go( &PCB_SELECTION_TOOL::selectNet,           PCB_ACTIONS::selectNet.MakeEvent() );
2768     Go( &PCB_SELECTION_TOOL::selectNet,           PCB_ACTIONS::deselectNet.MakeEvent() );
2769     Go( &PCB_SELECTION_TOOL::selectSameSheet,     PCB_ACTIONS::selectSameSheet.MakeEvent() );
2770     Go( &PCB_SELECTION_TOOL::selectSheetContents,
2771         PCB_ACTIONS::selectOnSheetFromEeschema.MakeEvent() );
2772     Go( &PCB_SELECTION_TOOL::updateSelection,     EVENTS::SelectedItemsModified );
2773     Go( &PCB_SELECTION_TOOL::updateSelection,     EVENTS::SelectedItemsMoved );
2774 
2775     Go( &PCB_SELECTION_TOOL::SelectAll,           ACTIONS::selectAll.MakeEvent() );
2776 
2777     Go( &PCB_SELECTION_TOOL::disambiguateCursor,  EVENTS::DisambiguatePoint );
2778 }
2779