KiCad PCB EDA Suite
Loading...
Searching...
No Matches
pcb_selection_tool.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2013-2017 CERN
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 * @author Tomasz Wlostowski <[email protected]>
7 * @author Maciej Suminski <[email protected]>
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, see <https://www.gnu.org/licenses/>.
21 */
22
23#include <algorithm>
24#include <cmath>
25#include <functional>
26#include <stack>
27using namespace std::placeholders;
28
29#include <advanced_config.h>
30#include <macros.h>
31#include <board.h>
33#include <footprint.h>
34#include <pad.h>
35#include <pcb_point.h>
36#include <pcb_drill_chart.h>
37#include <pcb_table.h>
38#include <pcb_tablecell.h>
39#include <pcb_track.h>
40#include <pcb_marker.h>
41#include <pad.h>
42#include <pcb_generator.h>
43#include <pcb_group.h>
44#include <pcb_base_edit_frame.h>
45#include <zone.h>
46#include <collectors.h>
48#include <view/view_controls.h>
49#include <gal/painter.h>
50#include <router/router_tool.h>
51#include <pcbnew_settings.h>
52#include <tool/action_menu.h>
53#include <tool/tool_event.h>
54#include <tool/tool_manager.h>
59#include <tools/pcb_actions.h>
62#include <trace_helpers.h>
64#include <wx/event.h>
65#include <wx/timer.h>
66#include <wx/log.h>
67#include <wx/debug.h>
68#include <core/profile.h>
69#include <math/vector2wx.h>
70
77
78
80{
81public:
83 ACTION_MENU( true )
84 {
85 SetTitle( _( "Select" ) );
86
88
89 AppendSeparator();
90
94
95 // This could be enabled if we have better logic for picking the target net with the mouse
96 // Add( PCB_ACTIONS::deselectNet );
99
102 }
103
104private:
105 ACTION_MENU* create() const override
106 {
107 return new SELECT_MENU();
108 }
109};
110
111enum
112{
113 ID_REPLACE_TERMINAL_PAD_A = wxID_HIGHEST + 3000,
115};
116
118{
119public:
121 {
122 SetTitle( _( "Set terminal pad" ) );
123 }
124
125protected:
126 ACTION_MENU* create() const override { return new REPLACE_TERMINAL_PAD_MENU(); }
127
128 void update() override
129 {
130 Clear();
131
132 TOOL_MANAGER* toolMgr = getToolManager();
133 if( !toolMgr )
134 return;
135
136 PCB_SELECTION_TOOL* selTool = toolMgr->GetTool<PCB_SELECTION_TOOL>();
137 if( !selTool )
138 return;
139
140 const SELECTION& sel = selTool->GetSelection();
141 if( sel.Empty() )
142 return;
143
144 PAD* pad = dynamic_cast<PAD*>( sel.Front() );
145 PCB_EDIT_FRAME* frame = static_cast<PCB_EDIT_FRAME*>( toolMgr->GetToolHolder() );
146
147 if( !pad || !frame )
148 return;
149
150 NETINFO_ITEM* net = pad->GetNet();
151
152 if( !net || net->GetNetChain().IsEmpty() )
153 return;
154
155 PAD* oldA = net->GetTerminalPad( 0 );
156 PAD* oldB = net->GetTerminalPad( 1 );
157 KIID newId = pad->m_Uuid;
158
159 wxMenuItem* itemA = Append( ID_REPLACE_TERMINAL_PAD_A, _( "Terminal A" ) );
160 wxMenuItem* itemB = Append( ID_REPLACE_TERMINAL_PAD_B, _( "Terminal B" ) );
161
162 if( oldA && oldA->m_Uuid == newId )
163 itemA->Enable( false );
164
165 if( oldB && oldB->m_Uuid == newId )
166 itemB->Enable( false );
167
168 m_oldA = oldA ? oldA->m_Uuid : niluuid;
169 m_oldB = oldB ? oldB->m_Uuid : niluuid;
170 m_new = newId;
171 }
172
173 OPT_TOOL_EVENT eventHandler( const wxMenuEvent& aEvent ) override
174 {
175 if( aEvent.GetId() == ID_REPLACE_TERMINAL_PAD_A )
176 {
178 te.SetParameter( std::make_pair( m_oldA.AsString(), m_new.AsString() ) );
179 return te;
180 }
181 else if( aEvent.GetId() == ID_REPLACE_TERMINAL_PAD_B )
182 {
184 te.SetParameter( std::make_pair( m_oldB.AsString(), m_new.AsString() ) );
185 return te;
186 }
187
188 return OPT_TOOL_EVENT();
189 }
190
191private:
195};
196
198{
199public:
201 {
202 SetTitle( _( "Net Chains..." ) );
206 }
207
208protected:
209 ACTION_MENU* create() const override { return new NET_CHAINS_MENU(); }
210
211private:
213};
214
215
224
225
227 SELECTION_TOOL( "common.InteractiveSelection" ),
228 m_frame( nullptr ),
229 m_isFootprintEditor( false ),
231 m_enteredGroup( nullptr ),
233 m_lockedItemsFiltered( false ),
234 m_previousFirstCell( nullptr ),
235 m_priv( std::make_unique<PRIV>() )
236{
237 m_filter.lockedItems = false;
238 m_filter.footprints = true;
239 m_filter.text = true;
240 m_filter.tracks = true;
241 m_filter.vias = true;
242 m_filter.pads = true;
243 m_filter.graphics = true;
244 m_filter.zones = true;
245 m_filter.keepouts = true;
246 m_filter.dimensions = true;
247 m_filter.points = true;
248 m_filter.gridItems = true;
249 m_filter.otherItems = true;
250}
251
252
254{
257
258 Disconnect( wxEVT_TIMER, wxTimerEventHandler( PCB_SELECTION_TOOL::onDisambiguationExpire ), nullptr, this );
259}
260
261
263{
265
266 if( frame && frame->IsType( FRAME_FOOTPRINT_VIEWER ) )
267 {
268 frame->AddStandardSubMenus( *m_menu.get() );
269 return true;
270 }
271
272 std::shared_ptr<SELECT_MENU> selectMenu = std::make_shared<SELECT_MENU>();
273 selectMenu->SetTool( this );
274 m_menu->RegisterSubMenu( selectMenu );
275
276 std::shared_ptr<NET_CHAINS_MENU> netChainsMenu = std::make_shared<NET_CHAINS_MENU>();
277 netChainsMenu->SetTool( this );
278 m_menu->RegisterSubMenu( netChainsMenu );
279
280 static const std::vector<KICAD_T> tableCellTypes = { PCB_TABLECELL_T };
281
282 auto& menu = m_menu->GetMenu();
283
284 auto activeToolCondition =
285 [this] ( const SELECTION& aSel )
286 {
288 return pcbFrame && !pcbFrame->ToolStackIsEmpty();
289 };
290
291 auto haveHighlight =
292 [this]( const SELECTION& sel )
293 {
294 KIGFX::RENDER_SETTINGS* cfg = m_toolMgr->GetView()->GetPainter()->GetSettings();
295
296 return !cfg->GetHighlightNetCodes().empty();
297 };
298
299 auto groupEnterCondition =
301
302 auto applyDesignBlockLayoutCondition = []( const SELECTION& aSel )
303 {
304 for( EDA_ITEM* item : aSel )
305 {
306 if( item->Type() == PCB_GROUP_T && static_cast<PCB_GROUP*>( item )->HasDesignBlockLink() )
307 {
308 return true;
309 }
310 }
311
312 return false;
313 };
314
315 auto inGroupCondition =
316 [this] ( const SELECTION& )
317 {
318 return m_enteredGroup != nullptr;
319 };
320
321 auto tableCellSelection = SELECTION_CONDITIONS::MoreThan( 0 )
323
324 SELECTION_CONDITION netItemSelection = []( const SELECTION& aSel )
325 {
326 if( aSel.GetSize() != 1 )
327 return false;
328 BOARD_ITEM* item = static_cast<BOARD_ITEM*>( *aSel.begin() );
329 return item->Type() == PCB_PAD_T || item->Type() == PCB_VIA_T || item->Type() == PCB_TRACE_T
330 || item->Type() == PCB_ARC_T;
331 };
332
333 if( frame && frame->IsType( FRAME_PCB_EDITOR ) )
334 {
335 menu.AddMenu( selectMenu.get(), SELECTION_CONDITIONS::NotEmpty );
336 menu.AddMenu( netChainsMenu.get(), netItemSelection );
337 menu.AddSeparator( 1000 );
338 }
339
340 // "Cancel" goes at the top of the context menu when a tool is active
341 menu.AddItem( ACTIONS::cancelInteractive, activeToolCondition, 1 );
342 menu.AddItem( ACTIONS::groupEnter, groupEnterCondition, 1 );
343 menu.AddItem( ACTIONS::groupLeave, inGroupCondition, 1 );
344 menu.AddItem( PCB_ACTIONS::applyDesignBlockLayout, applyDesignBlockLayoutCondition, 1 );
345 menu.AddItem( PCB_ACTIONS::placeLinkedDesignBlock, groupEnterCondition, 1 );
346 menu.AddItem( PCB_ACTIONS::saveToLinkedDesignBlock, groupEnterCondition, 1 );
347 menu.AddItem( PCB_ACTIONS::clearHighlight, haveHighlight, 1 );
348 menu.AddSeparator( haveHighlight, 1 );
349
350 menu.AddItem( ACTIONS::selectColumns, tableCellSelection, 2 );
351 menu.AddItem( ACTIONS::selectRows, tableCellSelection, 2 );
352 menu.AddItem( ACTIONS::selectTable, tableCellSelection, 2 );
353
354 menu.AddSeparator( 1 );
355
356 if( frame )
357 frame->AddStandardSubMenus( *m_menu.get() );
358
359 m_disambiguateTimer.SetOwner( this );
360 Connect( m_disambiguateTimer.GetId(), wxEVT_TIMER,
361 wxTimerEventHandler( PCB_SELECTION_TOOL::onDisambiguationExpire ), nullptr, this );
362
363 return true;
364}
365
366
368{
371
372 if( aReason != TOOL_BASE::REDRAW )
373 {
374 if( m_enteredGroup )
375 ExitGroup();
376
377 // Deselect any item being currently in edit, to avoid unexpected behavior and remove
378 // pointers to the selected items from containers.
379 ClearSelection( true );
380 }
381
382 if( aReason == TOOL_BASE::MODEL_RELOAD )
383 getView()->GetPainter()->GetSettings()->SetHighlight( false );
384
385 // Reinsert the VIEW_GROUP, in case it was removed from the VIEW
386 view()->Remove( &m_selection );
387 view()->Add( &m_selection );
388
391}
392
393
394void PCB_SELECTION_TOOL::OnIdle( wxIdleEvent& aEvent )
395{
396 if( m_frame->ToolStackIsEmpty() && !m_multiple )
397 {
398 wxMouseState keyboardState = wxGetMouseState();
399
400 setModifiersState( keyboardState.ShiftDown(), keyboardState.ControlDown(), keyboardState.AltDown() );
401
402 if( m_additive )
403 m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::ADD );
404 else if( m_subtractive )
405 m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::SUBTRACT );
406 else if( m_exclusive_or )
407 m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::XOR );
408 else
409 m_frame->GetCanvas()->SetCurrentCursor( m_nonModifiedCursor );
410 }
411}
412
413
415{
416 // Main loop: keep receiving events
417 while( TOOL_EVENT* evt = Wait() )
418 {
419 MOUSE_DRAG_ACTION dragAction = m_frame->GetDragAction();
421
422 if( PCBNEW_SETTINGS* cfg = m_frame->GetPcbNewSettings() )
423 trackDragAction = cfg->m_TrackDragAction;
424
425 // on left click, a selection is made, depending on modifiers ALT, SHIFT, CTRL:
426 setModifiersState( evt->Modifier( MD_SHIFT ), evt->Modifier( MD_CTRL ), evt->Modifier( MD_ALT ) );
427
429 bool brd_editor = frame && frame->IsType( FRAME_PCB_EDITOR );
430 ROUTER_TOOL* router = m_toolMgr->GetTool<ROUTER_TOOL>();
431
432 // If the router tool is active, don't override
433 if( router && router->IsToolActive() && router->RoutingInProgress() )
434 {
435 evt->SetPassEvent();
436 }
437 else if( evt->IsMouseDown( BUT_LEFT ) )
438 {
439 // Avoid triggering when running under other tools
440 PCB_POINT_EDITOR *pt_tool = m_toolMgr->GetTool<PCB_POINT_EDITOR>();
441
442 if( m_frame->ToolStackIsEmpty() && pt_tool && !pt_tool->HasPoint() )
443 {
444 m_originalCursor = m_toolMgr->GetMousePosition();
445 m_disambiguateTimer.StartOnce( ADVANCED_CFG::GetCfg().m_DisambiguationMenuDelay );
446 }
447 }
448 else if( evt->IsClick( BUT_LEFT ) )
449 {
450 // If there is no disambiguation, this routine is still running and will
451 // register a `click` event when released
452 if( m_disambiguateTimer.IsRunning() )
453 {
454 m_disambiguateTimer.Stop();
455
456 // A click on a geometric-constraint badge selects that constraint instead of a
457 // board item (the badges have no selectable geometry of their own); a click that
458 // misses every badge clears any badge selection.
459 if( CONSTRAINT_EDIT_TOOL* constraintTool =
460 m_toolMgr->GetTool<CONSTRAINT_EDIT_TOOL>() )
461 {
462 if( constraintTool->SelectConstraintAt( evt->Position() ) )
463 {
464 m_canceledMenu = false;
465 continue;
466 }
467
468 constraintTool->ClearConstraintSelection();
469 }
470
471 // Single click? Select single object
472 if( m_highlight_modifier && brd_editor )
473 {
474 if( !toggleTableCellSelection( evt->Position() ) )
476 }
477 else
478 {
479 m_frame->ClearFocus();
480
481 // Mirrors eeschema's SCH_TABLE shift+click range select.
482 if( !extendTableCellSelectionTo( evt->Position() ) )
483 {
484 selectPoint( evt->Position() );
485
486 // Anchor for a subsequent shift+click or shift+drag whose IsClick
487 // jitter could otherwise promote into IsDrag, collapsing the range
488 // rectangle to (DragOrigin, Position) at the press point.
490 }
491 }
492 }
493
494 m_canceledMenu = false;
495 }
496 else if( evt->IsClick( BUT_RIGHT ) )
497 {
498 m_disambiguateTimer.Stop();
499
500 // Right click? if there is any object - show the context menu
501 bool selectionCancelled = false;
502
503 if( m_selection.Empty() )
504 {
505 selectPoint( evt->Position(), false, &selectionCancelled );
506 m_selection.SetIsHover( true );
507 }
508
509 // Show selection before opening menu
510 m_frame->GetCanvas()->ForceRefresh();
511
512 if( !selectionCancelled )
513 {
514 m_toolMgr->VetoContextMenuMouseWarp();
515
516 // If every item in the selection shares the same generator parent and that
517 // generator provides a child context menu, show it instead of the standard
518 // selection menu. This lets a generator restrict what users can do to its
519 // (otherwise individually-selectable) children.
520 ACTION_MENU* genMenu = nullptr;
521 PCB_GENERATOR* sharedParent = nullptr;
522 bool allSameParent = !m_selection.Empty();
523
524 for( EDA_ITEM* item : m_selection )
525 {
526 if( !item->IsBOARD_ITEM() )
527 {
528 allSameParent = false;
529 break;
530 }
531
532 EDA_GROUP* parent = static_cast<BOARD_ITEM*>( item )->GetParentGroup();
533
534 if( !parent || parent->AsEdaItem()->Type() != PCB_GENERATOR_T )
535 {
536 allSameParent = false;
537 break;
538 }
539
540 PCB_GENERATOR* gen = static_cast<PCB_GENERATOR*>( parent->AsEdaItem() );
541
542 if( !sharedParent )
543 sharedParent = gen;
544 else if( sharedParent != gen )
545 {
546 allSameParent = false;
547 break;
548 }
549 }
550
551 if( allSameParent && sharedParent )
552 genMenu = sharedParent->GetChildContextMenu( this );
553
554 if( genMenu )
555 SetContextMenu( genMenu, CMENU_NOW );
556 else
557 m_menu->ShowContextMenu( m_selection );
558 }
559 }
560 else if( evt->IsDblClick( BUT_LEFT ) )
561 {
562 m_disambiguateTimer.Stop();
563
564 // Double clicks make no sense in the footprint viewer
565 if( frame && frame->IsType( FRAME_FOOTPRINT_VIEWER ) )
566 {
567 evt->SetPassEvent();
568 continue;
569 }
570
571 // A double-click on a constraint badge edits that relation's value.
572 if( CONSTRAINT_EDIT_TOOL* constraintTool = m_toolMgr->GetTool<CONSTRAINT_EDIT_TOOL>();
573 constraintTool && constraintTool->EditConstraintAt( evt->Position() ) )
574 {
575 continue;
576 }
577
578 // Double click? Display the properties window
579 m_frame->ClearFocus();
580
581 if( m_selection.Empty() )
582 selectPoint( evt->Position() );
583
584 if( m_selection.GetSize() == 1 && m_selection[0]->Type() == PCB_GROUP_T )
585 EnterGroup();
586 else if( !selectChartRow( evt->Position() ) )
588 }
589 else if( evt->IsDblClick( BUT_MIDDLE ) )
590 {
591 // Middle double click? Do zoom to fit or zoom to objects
592 if( evt->Modifier( MD_CTRL ) ) // Is CTRL key down?
594 else
595 m_toolMgr->RunAction( ACTIONS::zoomFitScreen );
596 }
597 else if( evt->Action() == TA_MOUSE_WHEEL )
598 {
599 int field = -1;
600
601 if( evt->Modifier() == ( MD_SHIFT | MD_ALT ) )
602 field = 0;
603 else if( evt->Modifier() == ( MD_CTRL | MD_ALT ) )
604 field = 1;
605 // any more?
606
607 if( field >= 0 )
608 {
609 const int delta = evt->Parameter<int>();
610 ACTIONS::INCREMENT params { delta > 0 ? 1 : -1, field };
611
612 m_toolMgr->RunAction( ACTIONS::increment, params );
613 }
614 }
615 else if( evt->IsDrag( BUT_LEFT ) )
616 {
617 m_disambiguateTimer.Stop();
618
619 // Is another tool already moving a new object? Don't allow a drag start
620 if( !m_selection.Empty() && m_selection[0]->HasFlag( IS_NEW | IS_MOVING ) )
621 {
622 evt->SetPassEvent();
623 continue;
624 }
625
626 // Drag with LMB? Select multiple objects (or at least draw a selection box)
627 // or drag them
628 m_frame->ClearFocus();
630
631 GENERAL_COLLECTOR hoverCells;
632 collectTableCellsAt( evt->DragOrigin(), hoverCells );
633
634 if( hoverCells.GetCount() )
635 {
636 if( m_selection.Empty() || SELECTION_CONDITIONS::OnlyTypes( { PCB_TABLECELL_T } )( m_selection ) )
637 {
638 selectTableCells( static_cast<PCB_TABLE*>( hoverCells[0]->GetParent() ) );
639 }
640 else
641 {
642 m_toolMgr->RunAction( PCB_ACTIONS::move );
643 }
644 }
645 else if( ( hasModifier() || dragAction == MOUSE_DRAG_ACTION::SELECT )
646 || ( m_selection.Empty() && dragAction != MOUSE_DRAG_ACTION::DRAG_ANY ) )
647 {
650 {
651 SelectRectArea( aEvent );
652 }
655 {
656 SelectPolyArea( aEvent );
657 }
658 else
659 {
660 wxASSERT_MSG( false, wxT( "Unknown selection mode" ) );
661 SelectRectArea( aEvent );
662 }
663 }
664 else
665 {
666 // Don't allow starting a drag from a zone filled area that isn't already selected
667 auto zoneFilledAreaFilter =
668 []( const VECTOR2I& aWhere, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* aTool )
669 {
670 int accuracy = aCollector.GetGuide()->Accuracy();
671 std::set<EDA_ITEM*> remove;
672
673 for( EDA_ITEM* item : aCollector )
674 {
675 if( item->Type() == PCB_ZONE_T )
676 {
677 ZONE* zone = static_cast<ZONE*>( item );
678
679 if( !zone->HitTestForCorner( aWhere, accuracy * 2 )
680 && !zone->HitTestForEdge( aWhere, accuracy ) )
681 {
682 remove.insert( zone );
683 }
684 }
685 }
686
687 for( EDA_ITEM* item : remove )
688 aCollector.Remove( item );
689 };
690
691 // See if we can drag before falling back to SelectRectArea()
692 bool doDrag = false;
693
694 if( evt->HasPosition() )
695 {
696 if( m_selection.Empty()
697 && selectPoint( evt->DragOrigin(), false, nullptr, zoneFilledAreaFilter ) )
698 {
699 m_selection.SetIsHover( true );
700 doDrag = true;
701 }
702 // Check if dragging has started within any of selected items bounding box.
703 else if( evt->HasPosition() && selectionContains( evt->DragOrigin() ) )
704 {
705 doDrag = true;
706 }
707 }
708
709 if( doDrag )
710 {
711 size_t segs = m_selection.CountType( PCB_TRACE_T );
712 size_t arcs = m_selection.CountType( PCB_ARC_T );
713 size_t vias = m_selection.CountType( PCB_VIA_T );
714 // Note: multi-track dragging is currently supported, but not multi-via
715 bool routable = ( segs >= 1 || arcs >= 1 || vias == 1 )
716 && ( segs + arcs + vias == m_selection.GetSize() );
717
718 // Vias that belong to a generator with individually-selectable children
719 // (e.g. via stitching) should use the plain move flow so the parent
720 // generator can react to the new child position. The PNS router would
721 // hijack the drag and skip that hook.
722 if( routable )
723 {
724 for( EDA_ITEM* item : m_selection )
725 {
726 if( !item->IsBOARD_ITEM() )
727 continue;
728
729 EDA_GROUP* parent = static_cast<BOARD_ITEM*>( item )->GetParentGroup();
730
731 if( !parent || parent->AsEdaItem()->Type() != PCB_GENERATOR_T )
732 continue;
733
734 PCB_GENERATOR* gen = static_cast<PCB_GENERATOR*>( parent->AsEdaItem() );
735
737 {
738 routable = false;
739 break;
740 }
741 }
742 }
743
744 if( routable && trackDragAction == TRACK_DRAG_ACTION::DRAG )
746 else if( routable && trackDragAction == TRACK_DRAG_ACTION::DRAG_FREE_ANGLE )
748 else
749 m_toolMgr->RunAction( PCB_ACTIONS::move );
750 }
751 else
752 {
753 // Otherwise drag a selection box
756 {
757 SelectPolyArea( aEvent );
758 }
759 else
760 {
761 SelectRectArea( aEvent );
762 }
763 }
764 }
765 }
766 else if( evt->IsCancel() )
767 {
768 m_disambiguateTimer.Stop();
769 m_frame->ClearFocus();
770
771 if( CONSTRAINT_EDIT_TOOL* constraintTool = m_toolMgr->GetTool<CONSTRAINT_EDIT_TOOL>();
772 constraintTool && constraintTool->ClearConstraintSelection() )
773 {
774 continue;
775 }
776
777 if( !GetSelection().Empty() )
778 {
780 }
781 else if( evt->FirstResponder() == this && evt->GetCommandId() == (int) WXK_ESCAPE )
782 {
783 if( m_enteredGroup )
784 {
785 ExitGroup();
786 }
787 else
788 {
789 BOARD_INSPECTION_TOOL* controller = m_toolMgr->GetTool<BOARD_INSPECTION_TOOL>();
790
791 try
792 {
793 if( controller && m_frame->GetPcbNewSettings()->m_ESCClearsNetHighlight )
794 controller->ClearHighlight( *evt );
795 }
796 catch( const std::runtime_error& e )
797 {
798 wxCHECK_MSG( false, 0, e.what() );
799 }
800 }
801 }
802 }
803 else
804 {
805 evt->SetPassEvent();
806 }
807
808
809 if( m_frame->ToolStackIsEmpty() )
810 {
811 // move cursor prediction
812 if( !hasModifier()
813 && dragAction == MOUSE_DRAG_ACTION::DRAG_SELECTED
814 && !m_selection.Empty()
815 && evt->HasPosition()
816 && selectionContains( evt->Position() ) )
817 {
819 }
820 else
821 {
823 }
824 }
825 }
826
827 // Shutting down; clear the selection
828 m_previousFirstCell = nullptr;
829 m_selection.Clear();
830 m_disambiguateTimer.Stop();
831
832 return 0;
833}
834
835
837{
838 wxCHECK_RET( m_selection.GetSize() == 1 && m_selection[0]->Type() == PCB_GROUP_T,
839 wxT( "EnterGroup called when selection is not a single group" ) );
840 PCB_GROUP* aGroup = static_cast<PCB_GROUP*>( m_selection[0] );
841
842 if( m_enteredGroup != nullptr )
843 ExitGroup();
844
846 m_enteredGroup = aGroup;
847 m_enteredGroup->SetFlags( ENTERED );
848
849 for( EDA_ITEM* member : m_enteredGroup->GetItems() )
850 select( member );
851
852 m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
853
854 // Processing the selection event can re-enter the tool and ExitGroup(), which clears
855 // m_enteredGroup. If that happened, don't operate on the now-stale (possibly null) group
856 // or we would hide/overlay a null item and crash (issue #24391).
857 if( m_enteredGroup != aGroup )
858 return;
859
860 view()->Hide( m_enteredGroup, true );
863}
864
865
866void PCB_SELECTION_TOOL::ExitGroup( bool aSelectGroup )
867{
868 // Only continue if there is a group entered
869 if( m_enteredGroup == nullptr )
870 return;
871
872 m_enteredGroup->ClearFlags( ENTERED );
873 view()->Hide( m_enteredGroup, false );
875
876 if( aSelectGroup )
877 {
879 m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
880 }
881
882 m_enteredGroupOverlay.Clear();
883 m_enteredGroup = nullptr;
885}
886
887
892
893
895{
896 bool selectionEmpty = m_selection.Empty();
897 m_selection.SetIsHover( selectionEmpty );
898 m_lockedItemsFiltered = false;
899
900 if( selectionEmpty )
901 {
902 m_toolMgr->RunAction( ACTIONS::selectionCursor, aClientFilter );
903 m_selection.ClearReferencePoint();
904 }
905
906 if( aClientFilter )
907 {
908 enum DISPOSITION { BEFORE = 1, AFTER, BOTH };
909
910 std::map<EDA_ITEM*, DISPOSITION> itemDispositions;
912 GENERAL_COLLECTOR collector;
913
914 collector.SetGuide( &guide );
915
916 for( EDA_ITEM* item : m_selection )
917 {
918 collector.Append( item );
919 itemDispositions[ item ] = BEFORE;
920 }
921
922 aClientFilter( VECTOR2I(), collector, this );
923
924 // Locked items were filtered with Override locks off. Keep the selection and return an
925 // empty one so the action does nothing. The banner then prompts to enable the override.
927 {
928 m_frame->GetCanvas()->ForceRefresh();
929 m_blockedSelection.Clear();
930 return m_blockedSelection;
931 }
932
933 for( EDA_ITEM* item : collector )
934 {
935 if( itemDispositions.count( item ) )
936 itemDispositions[ item ] = BOTH;
937 else
938 itemDispositions[ item ] = AFTER;
939 }
940
941 // Unhighlight the BEFORE items before highlighting the AFTER items.
942 // This is so that in the case of groups, if aClientFilter replaces a selection
943 // with the enclosing group, the unhighlight of the element doesn't undo the
944 // recursive highlighting of that element by the group.
945
946 for( std::pair<EDA_ITEM* const, DISPOSITION> itemDisposition : itemDispositions )
947 {
948 EDA_ITEM* item = itemDisposition.first;
949 DISPOSITION disposition = itemDisposition.second;
950
951 if( disposition == BEFORE )
953 }
954
955 for( std::pair<EDA_ITEM* const, DISPOSITION> itemDisposition : itemDispositions )
956 {
957 EDA_ITEM* item = itemDisposition.first;
958 DISPOSITION disposition = itemDisposition.second;
959
960 // Note that we must re-highlight even previously-highlighted items
961 // (ie: disposition BOTH) in case we removed any of their children.
962 if( disposition == AFTER || disposition == BOTH )
963 highlight( item, SELECTED, &m_selection );
964 }
965
966 m_frame->GetCanvas()->ForceRefresh();
967 }
968
969 return m_selection;
970}
971
972
974{
975 GENERAL_COLLECTORS_GUIDE guide( board()->GetVisibleLayers(), (PCB_LAYER_ID) view()->GetTopLayer(),
976 view() );
977
978 bool padsDisabled = !board()->IsElementVisible( LAYER_PADS );
979
980 // account for the globals
981 guide.SetIgnoreFPTextOnBack( !board()->IsElementVisible( LAYER_FP_TEXT ) );
982 guide.SetIgnoreFPTextOnFront( !board()->IsElementVisible( LAYER_FP_TEXT ) );
983 guide.SetIgnoreFootprintsOnBack( !board()->IsElementVisible( LAYER_FOOTPRINTS_BK ) );
984 guide.SetIgnoreFootprintsOnFront( !board()->IsElementVisible( LAYER_FOOTPRINTS_FR ) );
985 guide.SetIgnorePadsOnBack( padsDisabled );
986 guide.SetIgnorePadsOnFront( padsDisabled );
987 guide.SetIgnoreThroughHolePads( padsDisabled );
988 guide.SetIgnoreFPValues( !board()->IsElementVisible( LAYER_FP_VALUES ) );
989 guide.SetIgnoreFPReferences( !board()->IsElementVisible( LAYER_FP_REFERENCES ) );
990 guide.SetIgnoreThroughVias( ! board()->IsElementVisible( LAYER_VIAS ) );
991 guide.SetIgnoreBlindBuriedVias( ! board()->IsElementVisible( LAYER_VIAS ) );
992 guide.SetIgnoreMicroVias( ! board()->IsElementVisible( LAYER_VIAS ) );
993 guide.SetIgnoreTracks( ! board()->IsElementVisible( LAYER_TRACKS ) );
994
995 return guide;
996}
997
998
1000{
1001 return m_frame && m_frame->GetPcbNewSettings() && m_frame->GetPcbNewSettings()->m_CtrlClickHighlight && !m_isFootprintEditor;
1002}
1003
1004
1005bool PCB_SELECTION_TOOL::selectPoint( const VECTOR2I& aWhere, bool aOnDrag, bool* aSelectionCancelledFlag,
1006 CLIENT_SELECTION_FILTER aClientFilter )
1007{
1008 GENERAL_COLLECTOR collector;
1010 POINT_COLLECT options;
1011
1012 rejected.SetAll( false );
1013 options.m_OnDrag = aOnDrag;
1014 options.m_SelectedOnly = m_subtractive;
1015 options.m_Rejected = &rejected;
1016
1017 if( m_enteredGroup && !m_enteredGroup->GetBoundingBox().Contains( aWhere ) )
1018 ExitGroup();
1019
1020 m_selection.ClearReferencePoint();
1021
1022 if( !collectAtPoint( aWhere, collector, options, aClientFilter ) )
1023 return false;
1024
1025 // Nothing survived a filter that had something to take. Every step after it leaves an
1026 // empty collector alone, so this reads the same here as it did before them.
1027 if( collector.GetCount() == 0 && options.m_PreFilterCount > 0 )
1028 {
1029 if( PCB_BASE_EDIT_FRAME* editFrame = dynamic_cast<PCB_BASE_EDIT_FRAME*>( m_frame ) )
1030 editFrame->HighlightSelectionFilter( rejected );
1031
1032 if( !m_additive && !m_subtractive && !m_exclusive_or && m_selection.GetSize() > 0 )
1033 {
1034 ClearSelection( true /*quiet mode*/ );
1035 m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent );
1036 }
1037
1038 return false;
1039 }
1040
1041 // If still more than one item we're going to have to ask the user.
1042 if( collector.GetCount() > 1 )
1043 {
1044 if( aOnDrag )
1046
1047 if( !doSelectionMenu( &collector ) )
1048 {
1049 if( aSelectionCancelledFlag )
1050 *aSelectionCancelledFlag = true;
1051
1052 return false;
1053 }
1054 }
1055
1056 int addedCount = 0;
1057 bool anySubtracted = false;
1058
1060 {
1061 if( m_selection.GetSize() > 0 )
1062 {
1063 ClearSelection( true /*quiet mode*/ );
1064 anySubtracted = true;
1065 }
1066 }
1067
1068 if( collector.GetCount() > 0 )
1069 {
1070 for( int i = 0; i < collector.GetCount(); ++i )
1071 {
1072 if( m_subtractive || ( m_exclusive_or && collector[i]->IsSelected() ) )
1073 {
1074 unselect( collector[i] );
1075 anySubtracted = true;
1076 }
1077 else
1078 {
1079 select( collector[i] );
1080 addedCount++;
1081 }
1082 }
1083 }
1084
1085 if( addedCount == 1 )
1086 {
1087 m_toolMgr->ProcessEvent( EVENTS::PointSelectedEvent );
1088 return true;
1089 }
1090 else if( addedCount > 1 )
1091 {
1092 m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
1093 return true;
1094 }
1095 else if( anySubtracted )
1096 {
1097 m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent );
1098 return true;
1099 }
1100
1101 return false;
1102}
1103
1104
1105bool PCB_SELECTION_TOOL::selectCursor( bool aForceSelect, CLIENT_SELECTION_FILTER aClientFilter )
1106{
1107 if( aForceSelect || m_selection.Empty() )
1108 {
1109 ClearSelection( true /*quiet mode*/ );
1110 selectPoint( getViewControls()->GetCursorPosition( false ), false, nullptr, aClientFilter );
1111 }
1112
1113 return !m_selection.Empty();
1114}
1115
1116
1117// Some navigation actions are allowed in selectMultiple
1128
1129
1130static void passEvent( TOOL_EVENT* const aEvent, const TOOL_ACTION* const aAllowedActions[] )
1131{
1132 for( int i = 0; aAllowedActions[i]; ++i )
1133 {
1134 if( aEvent->IsAction( aAllowedActions[i] ) )
1135 {
1136 aEvent->SetPassEvent();
1137 break;
1138 }
1139 }
1140}
1141
1142
1144{
1145 for( PCB_TABLECELL* cell : aTable->GetCells() )
1146 {
1147 if( cell->IsSelected() )
1148 cell->SetFlags( CANDIDATE );
1149 else
1150 cell->ClearFlags( CANDIDATE );
1151 }
1152}
1153
1154
1156 PCB_TABLE* aTable )
1157{
1158 BOX2I selectionRect( aStart, aEnd );
1159 selectionRect.Normalize();
1160
1161 auto wasSelected =
1162 []( EDA_ITEM* aItem )
1163 {
1164 return ( aItem->GetFlags() & CANDIDATE ) > 0;
1165 };
1166
1167 for( PCB_TABLECELL* cell : aTable->GetCells() )
1168 {
1169 bool doSelect = false;
1170
1171 if( cell->HitTest( selectionRect, false ) )
1172 {
1173 if( m_subtractive )
1174 doSelect = false;
1175 else if( m_exclusive_or )
1176 doSelect = !wasSelected( cell );
1177 else
1178 doSelect = true;
1179 }
1180 else if( wasSelected( cell ) )
1181 {
1182 doSelect = m_additive || m_subtractive || m_exclusive_or;
1183 }
1184
1185 if( doSelect && !cell->IsSelected() )
1186 select( cell );
1187 else if( !doSelect && cell->IsSelected() )
1188 unselect( cell );
1189 }
1190}
1191
1192
1194{
1195 if( !m_additive || m_selection.GetSize() == 0
1196 || !dynamic_cast<PCB_TABLECELL*>( m_selection.GetItem( 0 ) ) )
1197 {
1198 return false;
1199 }
1200
1201 GENERAL_COLLECTOR clickCells;
1202 collectTableCellsAt( aPosition, clickCells );
1203
1204 if( clickCells.GetCount() != 1 )
1205 return false;
1206
1207 PCB_TABLECELL* clickedCell = static_cast<PCB_TABLECELL*>( clickCells[0] );
1208 PCB_TABLECELL* firstCell = static_cast<PCB_TABLECELL*>( m_selection.GetItem( 0 ) );
1209 PCB_TABLE* parentTable = static_cast<PCB_TABLE*>( clickedCell->GetParent() );
1210
1211 // Drop the cached anchor when the selection no longer holds only cells of the
1212 // clicked table, so it cannot survive into a later shift+drag on a different table.
1213 for( EDA_ITEM* item : m_selection )
1214 {
1215 if( !dynamic_cast<PCB_TABLECELL*>( item ) || item->GetParent() != parentTable )
1216 {
1217 m_previousFirstCell = nullptr;
1218 return false;
1219 }
1220 }
1221
1222 // Contains() prevents reading GetCenter() on a cell freed by an external mutation
1223 // that did not clear the cached anchor.
1225 m_previousFirstCell = firstCell;
1226
1227 if( m_previousFirstCell->GetParent() != parentTable )
1228 {
1229 m_previousFirstCell = nullptr;
1230 return false;
1231 }
1232
1233 // Snapshot the prior selection so we can fire SelectedEvent/UnselectedEvent based
1234 // on the net delta rather than on every range change.
1235 std::set<EDA_ITEM*> previousSelection;
1236
1237 for( EDA_ITEM* item : m_selection )
1238 previousSelection.insert( item );
1239
1240 // Restore main-view visibility of the previously-selected cells via the overlay
1241 // mechanism; ClearSelected() alone would leave them hidden because
1242 // highlightInternal/unhighlightInternal toggle view()->Hide.
1243 while( m_selection.GetSize() )
1244 unselect( m_selection.Front() );
1245
1246 initializeTableCellSelectionState( parentTable );
1247
1248 VECTOR2D start = m_previousFirstCell->GetCenter();
1249 VECTOR2D end = clickedCell->GetCenter();
1250 VECTOR2D topLeft( std::min( start.x, end.x ), std::min( start.y, end.y ) );
1251 VECTOR2D bottomRight( std::max( start.x, end.x ), std::max( start.y, end.y ) );
1252
1253 selectCellsBetween( topLeft, bottomRight - topLeft, parentTable );
1254
1255 bool anyAdded = false;
1256 bool anySubtracted = false;
1257
1258 for( PCB_TABLECELL* cell : parentTable->GetCells() )
1259 {
1260 bool wasInPrevious = previousSelection.count( cell ) > 0;
1261
1262 if( cell->IsSelected() && !wasInPrevious )
1263 anyAdded = true;
1264 else if( wasInPrevious && !cell->IsSelected() )
1265 anySubtracted = true;
1266 }
1267
1268 if( anyAdded )
1269 m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
1270
1271 if( anySubtracted )
1272 m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent );
1273
1274 return true;
1275}
1276
1277
1279 GENERAL_COLLECTOR& aCollector )
1280{
1282 ? static_cast<BOARD_ITEM*>( board()->GetFirstFootprint() )
1283 : static_cast<BOARD_ITEM*>( board() );
1284
1285 aCollector.Collect( scope, { PCB_TABLECELL_T }, aPosition, getCollectorsGuide() );
1286}
1287
1288
1290{
1291 if( m_selection.GetSize() != 1 )
1292 return nullptr;
1293
1294 return dynamic_cast<PCB_TABLECELL*>( m_selection.GetItem( 0 ) );
1295}
1296
1297
1299{
1300 if( m_selection.GetSize() == 0 )
1301 return false;
1302
1303 PCB_TABLECELL* firstCell = dynamic_cast<PCB_TABLECELL*>( m_selection.GetItem( 0 ) );
1304
1305 if( !firstCell )
1306 return false;
1307
1308 // Suppress highlightNet only when every selected item is a cell of the same table;
1309 // a mixed selection must fall through to the usual Ctrl+click action.
1310 PCB_TABLE* selectedTable = static_cast<PCB_TABLE*>( firstCell->GetParent() );
1311
1312 for( EDA_ITEM* item : m_selection )
1313 {
1314 PCB_TABLECELL* cell = dynamic_cast<PCB_TABLECELL*>( item );
1315
1316 if( !cell || cell->GetParent() != selectedTable )
1317 return false;
1318 }
1319
1320 GENERAL_COLLECTOR clickCells;
1321 collectTableCellsAt( aPosition, clickCells );
1322
1323 if( clickCells.GetCount() != 1 )
1324 return false;
1325
1326 PCB_TABLECELL* clickedCell = static_cast<PCB_TABLECELL*>( clickCells[0] );
1327
1328 if( clickedCell->GetParent() != selectedTable )
1329 return false;
1330
1331 if( clickedCell->IsSelected() )
1332 {
1333 unselect( clickedCell );
1334 m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent );
1335 }
1336 else
1337 {
1338 select( clickedCell );
1339 m_toolMgr->ProcessEvent( EVENTS::PointSelectedEvent );
1340 }
1341
1342 // A toggle breaks the contiguous-rectangle invariant the anchor represents.
1343 m_previousFirstCell = nullptr;
1344
1345 return true;
1346}
1347
1348
1350{
1351 bool cancelled = false; // Was the tool canceled while it was running?
1352 m_multiple = true; // Multiple selection mode is active
1353
1354 // Shift+click can jitter into IsDrag, collapsing DragOrigin..Position to the press
1355 // point; honour the cached anchor instead. Snapshot its coordinate so the drag loop
1356 // never dereferences a cell that an external mutation could free underneath us.
1357 bool haveAnchorStart = false;
1358 VECTOR2D anchorStart;
1359
1361 && m_previousFirstCell->GetParent() == aTable )
1362 {
1363 anchorStart = VECTOR2D( m_previousFirstCell->GetCenter() );
1364 haveAnchorStart = true;
1365 }
1366 else if( m_previousFirstCell && !m_selection.Contains( m_previousFirstCell ) )
1367 {
1368 m_previousFirstCell = nullptr;
1369 }
1370
1372
1373 auto wasSelected =
1374 []( EDA_ITEM* aItem )
1375 {
1376 return ( aItem->GetFlags() & CANDIDATE ) > 0;
1377 };
1378
1379 while( TOOL_EVENT* evt = Wait() )
1380 {
1381 if( evt->IsCancelInteractive() || evt->IsActivate() )
1382 {
1383 cancelled = true;
1384 break;
1385 }
1386 else if( evt->IsDrag( BUT_LEFT ) )
1387 {
1388 getViewControls()->SetAutoPan( true );
1389
1390 VECTOR2D start = haveAnchorStart ? anchorStart : VECTOR2D( evt->DragOrigin() );
1391 VECTOR2D end = VECTOR2D( evt->Position() );
1392
1393 selectCellsBetween( start, end - start, aTable );
1394 }
1395 else if( evt->IsMouseUp( BUT_LEFT ) )
1396 {
1397 m_selection.SetIsHover( false );
1398
1399 bool anyAdded = false;
1400 bool anySubtracted = false;
1401
1402 for( PCB_TABLECELL* cell : aTable->GetCells() )
1403 {
1404 if( cell->IsSelected() && !wasSelected( cell ) )
1405 anyAdded = true;
1406 else if( wasSelected( cell ) && !cell->IsSelected() )
1407 anySubtracted = true;
1408 }
1409
1410 // Inform other potentially interested tools
1411 if( anyAdded )
1412 m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
1413
1414 if( anySubtracted )
1415 m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent );
1416
1417 break; // Stop waiting for events
1418 }
1419 else
1420 {
1421 // Allow some actions for navigation
1422 passEvent( evt, allowedActions );
1423 }
1424 }
1425
1426 getViewControls()->SetAutoPan( false );
1427
1428 m_multiple = false; // Multiple selection mode is inactive
1429
1430 if( !cancelled )
1431 m_selection.ClearReferencePoint();
1432
1433 return cancelled;
1434}
1435
1436
1438{
1439 return DragSelectionArea( *this );
1440}
1441
1442
1444{
1445 bool cancelled = false; // Was the tool canceled while it was running?
1446 m_multiple = true; // Multiple selection mode is active
1448
1450 view->Add( &area );
1451
1452 while( TOOL_EVENT* evt = aTool.Wait() )
1453 {
1454 // Main() is not pumping events while another tool drives this loop. Track modifiers
1455 // here. On our own path Main() is awake and already latched them from the drag. Per
1456 // event there would drop a modifier released before the button. Command events carry
1457 // none.
1458 if( &aTool != this )
1459 setModifiersState( evt->Modifier( MD_SHIFT ), evt->Modifier( MD_CTRL ), evt->Modifier( MD_ALT ) );
1460
1461 /* Selection mode depends on direction of drag-selection:
1462 * Left > Right : Select objects that are fully enclosed by selection
1463 * Right > Left : Select objects that are crossed by selection
1464 */
1465 bool greedySelection = area.GetEnd().x < area.GetOrigin().x;
1466
1467 if( view->IsMirroredX() )
1468 greedySelection = !greedySelection;
1469
1470 m_frame->GetCanvas()->SetCurrentCursor( !greedySelection ? KICURSOR::SELECT_WINDOW
1472
1473 if( evt->IsCancelInteractive() || evt->IsActivate() )
1474 {
1475 cancelled = true;
1476 break;
1477 }
1478
1479 if( evt->IsDrag( BUT_LEFT ) )
1480 {
1482 {
1483 if( m_selection.GetSize() > 0 )
1484 {
1485 ClearSelection( true /*quiet mode*/ );
1486 m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent );
1487 }
1488 }
1489
1490 // Start drawing a selection box
1491 area.SetOrigin( evt->DragOrigin() );
1492 area.SetEnd( evt->Position() );
1495 area.SetExclusiveOr( false );
1497
1498 view->SetVisible( &area, true );
1499 view->Update( &area );
1500 getViewControls()->SetAutoPan( true );
1501
1502 if( aPreview )
1503 aPreview( area );
1504 }
1505
1506 if( evt->IsMouseUp( BUT_LEFT ) )
1507 {
1508 getViewControls()->SetAutoPan( false );
1509
1510 // End drawing the selection box
1511 view->SetVisible( &area, false );
1512
1514
1515 break; // Stop waiting for events
1516 }
1517
1518 // Allow some actions for navigation
1519 passEvent( evt, allowedActions );
1520 }
1521
1522 getViewControls()->SetAutoPan( false );
1523
1524 // Stop drawing the selection box
1525 view->Remove( &area );
1526 m_multiple = false; // Multiple selection mode is inactive
1527
1528 if( !cancelled )
1529 m_selection.ClearReferencePoint();
1530
1532
1533 return cancelled;
1534}
1535
1536
1538{
1540 m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::SELECT_LASSO );
1541 m_toolMgr->PostAction( ACTIONS::selectionTool );
1542 return 0; // No need to wait for an event, just set the mode
1543}
1544
1545
1547{
1549 m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::ARROW );
1550 m_toolMgr->PostAction( ACTIONS::selectionTool );
1551 return 0; // No need to wait for an event, just set the mode
1552}
1553
1554
1556{
1557 bool cancelled = false; // Was the tool canceled while it was running?
1558
1560 m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::SELECT_LASSO );
1561
1562
1563 SHAPE_LINE_CHAIN points;
1564 points.SetClosed( true );
1565
1567 getView()->Add( &area );
1568 getView()->SetVisible( &area, true );
1569 getViewControls()->SetAutoPan( true );
1570
1571 while( TOOL_EVENT* evt = Wait() )
1572 {
1573 // Auto mode: clockwise = inside, counterclockwise = touching
1574 double shapeArea = area.GetPoly().Area( false );
1575 bool isClockwise = shapeArea > 0 ? true : false;
1576
1577 if( getView()->IsMirroredX() && shapeArea != 0 )
1578 isClockwise = !isClockwise;
1579
1580 selectionMode = isClockwise ? SELECTION_MODE::INSIDE_LASSO : SELECTION_MODE::TOUCHING_LASSO;
1581
1582 if( evt->IsCancelInteractive() || evt->IsActivate() )
1583 {
1584 cancelled = true;
1585 evt->SetPassEvent( false );
1586 break;
1587 }
1588 else if( evt->IsDrag( BUT_LEFT )
1589 || evt->IsClick( BUT_LEFT )
1590 || evt->IsAction( &ACTIONS::cursorClick ) )
1591 {
1592 points.Append( evt->Position() );
1593 }
1594 else if( evt->IsDblClick( BUT_LEFT )
1595 || evt->IsAction( &ACTIONS::cursorDblClick )
1596 || evt->IsAction( &ACTIONS::finishInteractive ) )
1597 {
1598 area.GetPoly().GenerateBBoxCache();
1600 evt->SetPassEvent( false );
1601 break;
1602 }
1603 else if( evt->IsAction( &ACTIONS::deleteLastPoint )
1604 || evt->IsAction( &ACTIONS::doDelete )
1605 || evt->IsAction( &ACTIONS::undo ) )
1606 {
1607 if( points.GetPointCount() > 0 )
1608 {
1610 points.Remove( points.GetPointCount() - 1 );
1611 }
1612 }
1613 else
1614 {
1615 // Allow navigation actions
1616 passEvent( evt, allowedActions );
1617 }
1618
1619 if( points.PointCount() > 0 )
1620 {
1622 {
1623 if( m_selection.GetSize() > 0 )
1624 {
1625 ClearSelection( true /*quiet mode*/ );
1626 m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent );
1627 }
1628 }
1629 }
1630
1631 area.SetPoly( points );
1632 area.GetPoly().Append( m_toolMgr->GetMousePosition() );
1633 area.SetAdditive( m_additive );
1635 area.SetExclusiveOr( false );
1636 area.SetMode( selectionMode );
1637 getView()->Update( &area );
1638 }
1639
1640 getViewControls()->SetAutoPan( false );
1641 getView()->SetVisible( &area, false );
1642 getView()->Remove( &area );
1643 m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::ARROW );
1644
1645 if( !cancelled )
1647
1649
1650 return cancelled;
1651}
1652
1653
1655 POINT_COLLECT& aOptions, CLIENT_SELECTION_FILTER aClientFilter )
1656{
1658 const PCB_DISPLAY_OPTIONS& displayOpts = m_frame->GetDisplayOptions();
1659
1660 // Without this a zone's fill answers for every point inside it, and nothing on top of a
1661 // zone can ever be picked.
1663
1666 aWhere, guide );
1667
1668 for( int i = aCollector.GetCount() - 1; i >= 0; --i )
1669 {
1670 if( !Selectable( aCollector[i] ) || ( aOptions.m_OnDrag && aCollector[i]->IsLocked() ) )
1671 aCollector.Remove( i );
1672 }
1673
1674 aOptions.m_PreFilterCount = aCollector.GetCount();
1675
1676 FilterCollectedItems( aCollector, false, aOptions.m_Rejected );
1677
1678 // Narrow to what the caller can use before the heuristics run, so that they choose among
1679 // usable items rather than picking one the caller then has to throw away.
1680 if( aClientFilter )
1681 aClientFilter( aWhere, aCollector, this );
1682
1683 FilterCollectorForHierarchy( aCollector, false );
1684 FilterCollectorForFootprints( aCollector, aWhere );
1685
1686 if( aOptions.m_SelectedOnly )
1687 {
1688 for( int i = aCollector.GetCount() - 1; i >= 0; --i )
1689 {
1690 if( !aCollector[i]->IsSelected() )
1691 aCollector.Remove( i );
1692 }
1693 }
1694
1695 // Apply some ugly heuristics to avoid disambiguation menus whenever possible
1696 if( aCollector.GetCount() > 1 && !m_skip_heuristics )
1697 {
1698 try
1699 {
1700 GuessSelectionCandidates( aCollector, aWhere );
1701 }
1702 catch( const std::exception& exc )
1703 {
1704 wxLogWarning( wxS( "Exception '%s' occurred attempting to guess selection candidates." ),
1705 exc.what() );
1706 return false;
1707 }
1708 }
1709
1710 return true;
1711}
1712
1713
1714std::vector<BOARD_ITEM*> PCB_SELECTION_TOOL::CollectPoint( const VECTOR2I& aWhere,
1715 CLIENT_SELECTION_FILTER aClientFilter )
1716{
1717 GENERAL_COLLECTOR collector;
1718 POINT_COLLECT options;
1719
1720 // A hover shows what it can even when the heuristics gave up narrowing.
1721 collectAtPoint( aWhere, collector, options, aClientFilter );
1722
1723 std::vector<BOARD_ITEM*> items;
1724
1725 for( int i = 0; i < collector.GetCount(); ++i )
1726 items.push_back( collector[i] );
1727
1728 return items;
1729}
1730
1731
1733{
1735
1736 SELECTION_MODE selectionMode = aArea.GetMode();
1737 bool containedMode = ( selectionMode == SELECTION_MODE::INSIDE_RECTANGLE
1738 || selectionMode == SELECTION_MODE::INSIDE_LASSO ) ? true : false;
1739 bool boxMode = ( selectionMode == SELECTION_MODE::INSIDE_RECTANGLE
1740 || selectionMode == SELECTION_MODE::TOUCHING_RECTANGLE ) ? true : false;
1741
1742 std::vector<KIGFX::VIEW::LAYER_ITEM_PAIR> candidates;
1743 BOX2I selectionBox = aArea.ViewBBox();
1744 view->Query( selectionBox, candidates ); // Get the list of nearby items
1745
1746 GENERAL_COLLECTOR collector;
1747 GENERAL_COLLECTOR padsCollector;
1748 std::set<EDA_ITEM*> group_items;
1749
1750 for( PCB_GROUP* group : board()->Groups() )
1751 {
1752 // The currently entered group does not get limited
1753 if( m_enteredGroup == group )
1754 continue;
1755
1756 std::unordered_set<EDA_ITEM*>& newset = group->GetItems();
1757
1758 auto boxContained =
1759 [&]( const BOX2I& aBox )
1760 {
1761 return boxMode ? selectionBox.Contains( aBox )
1762 : KIGEOM::BoxHitTest( aArea.GetPoly(), aBox, true );
1763 };
1764
1765 // If we are not greedy and have selected the whole group, add just one item
1766 // to allow it to be promoted to the group later
1767 if( containedMode && boxContained( group->GetBoundingBox() ) && newset.size() )
1768 {
1769 for( EDA_ITEM* group_item : newset )
1770 {
1771 if( !group_item->IsBOARD_ITEM() )
1772 continue;
1773
1774 if( Selectable( static_cast<BOARD_ITEM*>( group_item ) ) )
1775 collector.Append( *newset.begin() );
1776 }
1777 }
1778
1779 for( EDA_ITEM* group_item : newset )
1780 group_items.emplace( group_item );
1781 }
1782
1783 auto hitTest =
1784 [&]( const EDA_ITEM* aItem )
1785 {
1786 return boxMode ? aItem->HitTest( selectionBox, containedMode )
1787 : aItem->HitTest( aArea.GetPoly(), containedMode );
1788 };
1789
1790 for( const auto& [item, layer] : candidates )
1791 {
1792 if( !item->IsBOARD_ITEM() )
1793 continue;
1794
1795 BOARD_ITEM* boardItem = static_cast<BOARD_ITEM*>( item );
1796
1797 if( Selectable( boardItem ) && hitTest( boardItem )
1798 && ( !containedMode || !group_items.count( boardItem ) ) )
1799 {
1800 if( boardItem->Type() == PCB_PAD_T && !m_isFootprintEditor )
1801 padsCollector.Append( boardItem );
1802 else
1803 collector.Append( boardItem );
1804 }
1805 }
1806
1807 // Apply the stateful filter
1808 FilterCollectedItems( collector, true, nullptr );
1809
1810 FilterCollectorForHierarchy( collector, true );
1811
1812 // If we selected nothing but pads, allow them to be selected
1813 if( collector.GetCount() == 0 )
1814 {
1815 collector = padsCollector;
1816 FilterCollectedItems( collector, true, nullptr );
1817 FilterCollectorForHierarchy( collector, true );
1818 }
1819
1820 // Sort the filtered selection by rows and columns to have a nice default
1821 // for tools that can use it.
1822 std::sort( collector.begin(), collector.end(),
1823 []( EDA_ITEM* a, EDA_ITEM* b )
1824 {
1825 VECTOR2I aPos = a->GetPosition();
1826 VECTOR2I bPos = b->GetPosition();
1827
1828 if( aPos.y == bPos.y )
1829 return aPos.x < bPos.x;
1830
1831 return aPos.y < bPos.y;
1832 } );
1833
1834 std::vector<BOARD_ITEM*> items;
1835
1836 for( EDA_ITEM* i : collector )
1837 {
1838 if( i->IsBOARD_ITEM() )
1839 items.push_back( static_cast<BOARD_ITEM*>( i ) );
1840 }
1841
1842 return items;
1843}
1844
1845
1847 bool aExclusiveOr )
1848{
1849 bool anyAdded = false;
1850 bool anySubtracted = false;
1851
1852 for( BOARD_ITEM* item : CollectMultiple( aArea ) )
1853 {
1854 if( aSubtractive || ( aExclusiveOr && item->IsSelected() ) )
1855 {
1856 unselect( item );
1857 anySubtracted = true;
1858 }
1859 else
1860 {
1861 select( item );
1862 anyAdded = true;
1863 }
1864 }
1865
1866 m_selection.SetIsHover( false );
1867
1868 // Inform other potentially interested tools
1869 if( anyAdded )
1870 m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
1871 else if( anySubtracted )
1872 m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent );
1873}
1874
1875
1877{
1878 wxMouseState keyboardState = wxGetMouseState();
1879
1880 setModifiersState( keyboardState.ShiftDown(), keyboardState.ControlDown(), keyboardState.AltDown() );
1881
1882 m_skip_heuristics = true;
1884 m_skip_heuristics = false;
1885
1886 return 0;
1887}
1888
1889
1890
1892{
1894
1895 selectCursor( false, aClientFilter );
1896
1897 return 0;
1898}
1899
1900
1902{
1904
1905 return 0;
1906}
1907
1908
1910{
1911 GENERAL_COLLECTOR collection;
1912 BOX2I selectionBox;
1913
1914 selectionBox.SetMaximum();
1915
1916 getView()->Query( selectionBox,
1917 [&]( KIGFX::VIEW_ITEM* viewItem ) -> bool
1918 {
1919 if( viewItem->IsBOARD_ITEM() )
1920 {
1921 BOARD_ITEM* item = static_cast<BOARD_ITEM*>( viewItem );
1922
1923 if( item && Selectable( item ) && itemPassesFilter( item, true, nullptr ) )
1924 collection.Append( item );
1925 }
1926
1927 return true;
1928 } );
1929
1930 FilterCollectorForHierarchy( collection, true );
1931
1932 for( EDA_ITEM* item : collection )
1933 select( item );
1934
1935 m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
1936
1937 m_frame->GetCanvas()->ForceRefresh();
1938
1939 return 0;
1940}
1941
1942
1944{
1945 BOX2I selectionBox;
1946
1947 selectionBox.SetMaximum();
1948
1949 getView()->Query( selectionBox,
1950 [&]( KIGFX::VIEW_ITEM* viewItem ) -> bool
1951 {
1952 if( viewItem->IsBOARD_ITEM() )
1953 {
1954 BOARD_ITEM* item = static_cast<BOARD_ITEM*>( viewItem );
1955
1956 if( item && Selectable( item ) )
1957 unselect( item );
1958 }
1959
1960 return true;
1961 } );
1962
1963 m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent );
1964
1965 m_frame->GetCanvas()->ForceRefresh();
1966
1967 return 0;
1968}
1969
1970
1972{
1973 // Narrow the collection down to a single BOARD_CONNECTED_ITEM for each represented net.
1974 // All other items types are removed.
1975 std::set<int> representedNets;
1976
1977 for( int i = aCollector.GetCount() - 1; i >= 0; i-- )
1978 {
1979 BOARD_CONNECTED_ITEM* item = dynamic_cast<BOARD_CONNECTED_ITEM*>( aCollector[i] );
1980
1981 if( !item )
1982 aCollector.Remove( i );
1983 else if ( representedNets.count( item->GetNetCode() ) )
1984 aCollector.Remove( i );
1985 else
1986 representedNets.insert( item->GetNetCode() );
1987 }
1988}
1989
1990
1992{
1993 std::deque<EDA_ITEM*> selectedItems = m_selection.GetItems();
1994
1995 // Get all footprints and pads
1996 std::vector<BOARD_CONNECTED_ITEM*> toUnroute;
1997 std::set<EDA_ITEM*> toDelete;
1998
1999 for( EDA_ITEM* item : selectedItems )
2000 {
2001 if( item->Type() == PCB_FOOTPRINT_T )
2002 {
2003 for( PAD* pad : static_cast<FOOTPRINT*>( item )->Pads() )
2004 toUnroute.push_back( pad );
2005 }
2006 else if( item->Type() == PCB_GENERATOR_T )
2007 {
2008 toDelete.insert( item );
2009
2010 for( BOARD_ITEM* generatedItem : static_cast<PCB_GENERATOR*>( item )->GetBoardItems() )
2011 {
2012 if( BOARD_CONNECTED_ITEM::ClassOf( generatedItem ) )
2013 toUnroute.push_back( static_cast<BOARD_CONNECTED_ITEM*>( generatedItem ) );
2014 }
2015 }
2016 else if( BOARD_CONNECTED_ITEM::ClassOf( item ) )
2017 {
2018 toUnroute.push_back( static_cast<BOARD_CONNECTED_ITEM*>( item ) );
2019 }
2020 }
2021
2022 // Find generators connected to tracks and add their children to toUnroute
2023 // so selectAllConnectedTracks can traverse through meanders
2024 std::set<int> selectedNets;
2025
2026 for( BOARD_CONNECTED_ITEM* item : toUnroute )
2027 if( item->GetNetCode() > 0 )
2028 selectedNets.insert( item->GetNetCode() );
2029
2030 std::set<VECTOR2I> endpointSet;
2031
2032 for( BOARD_CONNECTED_ITEM* item : toUnroute )
2033 {
2034 if( PCB_TRACK* track = dynamic_cast<PCB_TRACK*>( item ) )
2035 {
2036 endpointSet.insert( track->GetStart() );
2037 endpointSet.insert( track->GetEnd() );
2038 }
2039 else if( item->Type() == PCB_VIA_T )
2040 {
2041 endpointSet.insert( item->GetPosition() );
2042 }
2043 else if( item->Type() == PCB_PAD_T )
2044 {
2045 endpointSet.insert( item->GetPosition() );
2046 }
2047 }
2048
2049 bool expanded = true;
2050
2051 while( expanded )
2052 {
2053 expanded = false;
2054
2055 for( PCB_GENERATOR* gen : board()->Generators() )
2056 {
2057 if( toDelete.count( gen ) )
2058 continue;
2059
2060 // Find this generator's external endpoints (meander entry/exit)
2061 std::map<VECTOR2I, int> epCount;
2062
2063 for( BOARD_ITEM* child : gen->GetBoardItems() )
2064 {
2065 PCB_TRACK* track = dynamic_cast<PCB_TRACK*>( child );
2066
2067 if( track && selectedNets.count( track->GetNetCode() ) )
2068 {
2069 epCount[track->GetStart()]++;
2070 epCount[track->GetEnd()]++;
2071 }
2072 }
2073
2074 // Check if any external endpoint matches our track endpoints
2075 bool connected = false;
2076
2077 for( auto& [pt, count] : epCount )
2078 {
2079 if( count == 1 && endpointSet.count( pt ) )
2080 {
2081 connected = true;
2082 break;
2083 }
2084 }
2085
2086 if( connected )
2087 {
2088 toDelete.insert( gen );
2089
2090 for( BOARD_ITEM* c : gen->GetBoardItems() )
2091 {
2093 continue;
2094
2095 if( !selectedNets.count( static_cast<BOARD_CONNECTED_ITEM*>( c )->GetNetCode() ) )
2096 continue;
2097
2098 toUnroute.push_back( static_cast<BOARD_CONNECTED_ITEM*>( c ) );
2099
2100 if( PCB_TRACK* track = dynamic_cast<PCB_TRACK*>( c ) )
2101 {
2102 endpointSet.insert( track->GetStart() );
2103 endpointSet.insert( track->GetEnd() );
2104 }
2105
2106 expanded = true;
2107 }
2108 }
2109 }
2110 }
2111
2112 // Because selectAllConnectedTracks() use m_filter to collect connected tracks
2113 // to pads, enable filter for these items, regardless the curent filter options
2114 // via filter is not changed, because it can be useful to keep via filter disabled
2115 struct PCB_SELECTION_FILTER_OPTIONS save_filter = m_filter;
2116 m_filter.tracks = true;
2117 m_filter.pads = true;
2118
2119 // Clear selection so we don't delete our footprints/pads
2120 ClearSelection( true );
2121
2122 // Get the tracks on our list of pads, then delete them
2124
2125 BOARD_COMMIT commit( m_toolMgr );
2126 std::set<BOARD_ITEM*> removed;
2127
2128 for( EDA_ITEM* item : m_selection )
2129 {
2130 if( !item->IsBOARD_ITEM() )
2131 continue;
2132
2133 BOARD_ITEM* bi = static_cast<BOARD_ITEM*>( item );
2134
2135 if( bi->Type() == PCB_GENERATOR_T )
2136 toDelete.insert( bi );
2137
2138 commit.Remove( bi );
2139 removed.insert( bi );
2140 }
2141
2142 // Find generators whose children were removed
2143 for( PCB_GENERATOR* gen : board()->Generators() )
2144 {
2145 for( BOARD_ITEM* child : gen->GetBoardItems() )
2146 {
2147 if( removed.count( child ) )
2148 {
2149 toDelete.insert( gen );
2150 break;
2151 }
2152 }
2153 }
2154
2155 for( EDA_ITEM* item : toDelete )
2156 {
2157 if( !item->IsBOARD_ITEM() )
2158 continue;
2159
2160 BOARD_ITEM* boardItem = static_cast<BOARD_ITEM*>( item );
2161
2162 boardItem->RunOnChildren(
2163 [&commit, &removed]( BOARD_ITEM* aItem )
2164 {
2165 if( removed.find( aItem ) == removed.end() )
2166 {
2167 commit.Remove( aItem );
2168 removed.insert( aItem );
2169 }
2170 },
2172
2173 if( removed.find( boardItem ) == removed.end() )
2174 commit.Remove( boardItem );
2175 }
2176
2177 ClearSelection( true );
2178 commit.Push( _( "Unroute Selected" ) );
2179
2180 m_filter = save_filter; // restore current filter options
2181
2182 // Reselect our footprint/pads as they were in our original selection
2183 for( EDA_ITEM* item : selectedItems )
2184 {
2185 if( item->Type() == PCB_FOOTPRINT_T || item->Type() == PCB_PAD_T )
2186 select( item );
2187 }
2188
2189 return 0;
2190}
2191
2192
2194{
2195 std::deque<EDA_ITEM*> selectedItems = m_selection.GetItems();
2196
2197 // Get all footprints and pads
2198 std::vector<BOARD_CONNECTED_ITEM*> toUnroute;
2199
2200 std::set<EDA_ITEM*> toDelete;
2201
2202 for( EDA_ITEM* item : selectedItems )
2203 {
2204 if( item->Type() == PCB_TRACE_T || item->Type() == PCB_ARC_T || item->Type() == PCB_VIA_T )
2205 {
2206 BOARD_ITEM* bi = static_cast<BOARD_ITEM*>( item );
2207 EDA_GROUP* parentGroup = bi->GetParentGroup();
2208
2209 if( parentGroup && parentGroup->AsEdaItem()->Type() == PCB_GENERATOR_T )
2210 {
2211 PCB_GENERATOR* gen = static_cast<PCB_GENERATOR*>( parentGroup->AsEdaItem() );
2212
2213 if( !toDelete.count( gen ) )
2214 {
2215 toDelete.insert( gen );
2216
2217 for( BOARD_ITEM* generatedItem : gen->GetBoardItems() )
2218 {
2219 toDelete.insert( generatedItem );
2220
2221 if( BOARD_CONNECTED_ITEM::ClassOf( generatedItem ) )
2222 toUnroute.push_back( static_cast<BOARD_CONNECTED_ITEM*>( generatedItem ) );
2223 }
2224 }
2225 }
2226 else
2227 {
2228 toUnroute.push_back( static_cast<BOARD_CONNECTED_ITEM*>( item ) );
2229 toDelete.insert( item );
2230 }
2231 }
2232 else if( item->Type() == PCB_GENERATOR_T )
2233 {
2234 toDelete.insert( item );
2235
2236 for( BOARD_ITEM* generatedItem : static_cast<PCB_GENERATOR*>( item )->GetBoardItems() )
2237 {
2238 toDelete.insert( generatedItem );
2239
2240 if( BOARD_CONNECTED_ITEM::ClassOf( generatedItem ) )
2241 toUnroute.push_back( static_cast<BOARD_CONNECTED_ITEM*>( generatedItem ) );
2242 }
2243 }
2244 }
2245
2246 // Get the tracks connecting to our starting objects
2249 std::deque<EDA_ITEM*> toSelectAfter;
2250 // This will select the unroute items too, so filter them out
2251 for( EDA_ITEM* item : m_selection.GetItemsSortedByTypeAndXY() )
2252 {
2253 if( !item->IsBOARD_ITEM() )
2254 continue;
2255
2256 if( toDelete.find( item ) == toDelete.end() )
2257 toSelectAfter.push_back( item );
2258 }
2259
2260 BOARD_COMMIT commit( m_toolMgr );
2261 std::set<BOARD_ITEM*> removed;
2262
2263 for( EDA_ITEM* item : toDelete )
2264 {
2265 if( !item->IsBOARD_ITEM() )
2266 continue;
2267
2268 BOARD_ITEM* boardItem = static_cast<BOARD_ITEM*>( item );
2269
2270 if( item->Type() == PCB_GENERATOR_T )
2271 {
2272 boardItem->RunOnChildren(
2273 [&commit, &removed]( BOARD_ITEM* aItem )
2274 {
2275 if( removed.insert( aItem ).second )
2276 commit.Remove( aItem );
2277 },
2279
2280 if( removed.insert( boardItem ).second )
2281 commit.Remove( boardItem );
2282 }
2283 else
2284 {
2285 if( removed.insert( boardItem ).second )
2286 commit.Remove( boardItem );
2287 }
2288 }
2289
2290 commit.Push( _( "Unroute Segment" ) );
2291
2292 // Now our after tracks so the user can continue backing up as desired
2293 ClearSelection( true );
2294
2295 for( EDA_ITEM* item : toSelectAfter )
2296 {
2297 if( !toDelete.count( item ) )
2298 select( item );
2299 }
2300
2301 return 0;
2302}
2303
2304
2306{
2307 // expandConnection will get called no matter whether the user selected a connected item or a
2308 // non-connected shape (graphic on a non-copper layer). The algorithm for expanding to connected
2309 // items is different from graphics, so they need to be handled separately.
2310 unsigned initialCount = 0;
2311
2312 for( const EDA_ITEM* item : m_selection.GetItems() )
2313 {
2314 if( item->Type() == PCB_FOOTPRINT_T
2315 || item->Type() == PCB_GENERATOR_T
2316 || ( static_cast<const BOARD_ITEM*>( item )->IsConnected() ) )
2317 {
2318 initialCount++;
2319 }
2320 }
2321
2322 if( initialCount == 0 )
2323 {
2324 // First, process any graphic shapes we have
2325 std::vector<PCB_SHAPE*> startShapes;
2326
2327 for( EDA_ITEM* item : m_selection.GetItems() )
2328 {
2329 if( isExpandableGraphicShape( item ) )
2330 startShapes.push_back( static_cast<PCB_SHAPE*>( item ) );
2331 }
2332
2333 // If no non-copper shapes; fall back to looking for connected items
2334 if( !startShapes.empty() )
2335 selectAllConnectedShapes( startShapes );
2336 else
2338 }
2339
2340 m_frame->SetStatusText( _( "Select/Expand Connection..." ) );
2341
2342 for( STOP_CONDITION stopCondition : { STOP_AT_JUNCTION, STOP_AT_PAD, STOP_NEVER } )
2343 {
2344 std::deque<EDA_ITEM*> selectedItems = m_selection.GetItems();
2345
2346 for( EDA_ITEM* item : selectedItems )
2347 item->ClearTempFlags();
2348
2349 std::vector<BOARD_CONNECTED_ITEM*> startItems;
2350
2351 for( EDA_ITEM* item : selectedItems )
2352 {
2353 if( item->Type() == PCB_FOOTPRINT_T )
2354 {
2355 FOOTPRINT* footprint = static_cast<FOOTPRINT*>( item );
2356
2357 for( PAD* pad : footprint->Pads() )
2358 startItems.push_back( pad );
2359 }
2360 else if( item->Type() == PCB_GENERATOR_T )
2361 {
2362 for( BOARD_ITEM* generatedItem : static_cast<PCB_GENERATOR*>( item )->GetBoardItems() )
2363 {
2364 if( BOARD_CONNECTED_ITEM::ClassOf( generatedItem ) )
2365 startItems.push_back( static_cast<BOARD_CONNECTED_ITEM*>( generatedItem ) );
2366 }
2367 }
2368 else if( BOARD_CONNECTED_ITEM::ClassOf( item ) )
2369 {
2370 startItems.push_back( static_cast<BOARD_CONNECTED_ITEM*>( item ) );
2371 }
2372 }
2373
2374 selectAllConnectedTracks( startItems, stopCondition );
2375
2376 if( m_selection.GetItems().size() > initialCount )
2377 break;
2378 }
2379
2380 m_frame->SetStatusText( wxEmptyString );
2381
2382 // Inform other potentially interested tools
2383 m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
2384
2385 return 0;
2386}
2387
2388
2389void PCB_SELECTION_TOOL::selectAllConnectedTracks( const std::vector<BOARD_CONNECTED_ITEM*>& aStartItems,
2390 STOP_CONDITION aStopCondition )
2391{
2392 PROF_TIMER refreshTimer;
2393 double refreshIntervalMs = 500; // Refresh display with this interval to indicate progress
2394 int lastSelectionSize = (int) m_selection.GetSize();
2395
2396 auto connectivity = board()->GetConnectivity();
2397
2398 // Don't let expansion select outside an entered group, or select() would ExitGroup mid-walk.
2399 auto inScope = [this]( BOARD_ITEM* aItem )
2400 {
2402 };
2403
2404 std::set<PAD*> startPadSet;
2405 std::vector<BOARD_CONNECTED_ITEM*> cleanupItems;
2406
2407 for( BOARD_CONNECTED_ITEM* startItem : aStartItems )
2408 {
2409 // Register starting pads
2410 if( startItem->Type() == PCB_PAD_T )
2411 startPadSet.insert( static_cast<PAD*>( startItem ) );
2412
2413 // Select any starting track items
2414 if( startItem->IsType( { PCB_TRACE_T, PCB_ARC_T, PCB_VIA_T } ) )
2415 {
2416 if( itemPassesFilter( startItem, true ) && inScope( startItem ) )
2417 select( startItem );
2418 }
2419 }
2420
2421 for( BOARD_CONNECTED_ITEM* startItem : aStartItems )
2422 {
2423 std::map<VECTOR2I, std::vector<PCB_TRACK*>> trackMap;
2424 std::map<VECTOR2I, std::vector<PCB_VIA*>> viaMap;
2425 std::map<VECTOR2I, PAD*> padMap;
2426 std::map<VECTOR2I, std::vector<PCB_SHAPE*>> shapeMap;
2427 std::vector<std::pair<VECTOR2I, LSET>> activePts;
2428
2429 if( startItem->HasFlag( SKIP_STRUCT ) ) // Skip already visited items
2430 continue;
2431
2432 auto connectedItems = connectivity->GetConnectedItems( startItem, EXCLUDE_ZONES | IGNORE_NETS );
2433
2434 // Build maps of connected items
2435 for( BOARD_CONNECTED_ITEM* item : connectedItems )
2436 {
2437 switch( item->Type() )
2438 {
2439 case PCB_ARC_T:
2440 case PCB_TRACE_T:
2441 {
2442 PCB_TRACK* track = static_cast<PCB_TRACK*>( item );
2443 trackMap[track->GetStart()].push_back( track );
2444 trackMap[track->GetEnd()].push_back( track );
2445 break;
2446 }
2447
2448 case PCB_VIA_T:
2449 {
2450 PCB_VIA* via = static_cast<PCB_VIA*>( item );
2451
2452 // The hops of a stacked microvia stack are coaxial, so one position can hold
2453 // several vias.
2454 viaMap[via->GetStart()].push_back( via );
2455 break;
2456 }
2457
2458 case PCB_PAD_T:
2459 {
2460 PAD* pad = static_cast<PAD*>( item );
2461 padMap[pad->GetPosition()] = pad;
2462 break;
2463 }
2464
2465 case PCB_SHAPE_T:
2466 {
2467 PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( item );
2468
2469 for( const auto& point : shape->GetConnectionPoints() )
2470 shapeMap[point].push_back( shape );
2471
2472 break;
2473 }
2474
2475 default:
2476 break;
2477 }
2478 }
2479
2480 // Set up the initial active points
2481 switch( startItem->Type() )
2482 {
2483 case PCB_ARC_T:
2484 case PCB_TRACE_T:
2485 {
2486 PCB_TRACK* track = static_cast<PCB_TRACK*>( startItem );
2487
2488 activePts.push_back( { track->GetStart(), track->GetLayerSet() } );
2489 activePts.push_back( { track->GetEnd(), track->GetLayerSet() } );
2490 break;
2491 }
2492
2493 case PCB_VIA_T:
2494 activePts.push_back( { startItem->GetPosition(), startItem->GetLayerSet() } );
2495 break;
2496
2497 case PCB_PAD_T:
2498 activePts.push_back( { startItem->GetPosition(), startItem->GetLayerSet() } );
2499 break;
2500
2501 case PCB_SHAPE_T:
2502 {
2503 PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( startItem );
2504
2505 for( const auto& point : shape->GetConnectionPoints() )
2506 activePts.push_back( { point, startItem->GetLayerSet() } );
2507
2508 break;
2509 }
2510
2511 default:
2512 break;
2513 }
2514
2515 bool expand = true;
2516 int failSafe = 0;
2517
2518 // Iterative push from all active points
2519 while( expand && failSafe++ < 100000 )
2520 {
2521 expand = false;
2522
2523 for( int i = (int) activePts.size() - 1; i >= 0; --i )
2524 {
2525 VECTOR2I pt = activePts[i].first;
2526 LSET layerSetCu = activePts[i].second & LSET::AllCuMask();
2527
2528 PCB_VIA* hitVia = nullptr;
2529
2530 // Prefer an unvisited via so a stack is walked hop by hop.
2531 auto better = [&]( PCB_VIA* aCandidate )
2532 {
2533 return !hitVia || ( hitVia->HasFlag( SKIP_STRUCT ) && !aCandidate->HasFlag( SKIP_STRUCT ) );
2534 };
2535
2536 // An unvisited hit is the best there is, so stop looking for one.
2537 auto settled = [&]()
2538 {
2539 return hitVia && !hitVia->HasFlag( SKIP_STRUCT );
2540 };
2541
2542 // exact position match (common case)
2543 auto exactIt = viaMap.find( pt );
2544
2545 if( exactIt != viaMap.end() )
2546 {
2547 for( PCB_VIA* via : exactIt->second )
2548 {
2549 if( ( via->GetLayerSet() & layerSetCu ).any() && better( via ) )
2550 hitVia = via;
2551
2552 if( settled() )
2553 break;
2554 }
2555 }
2556
2557 if( !hitVia )
2558 {
2559 // off-center VIA connection
2560 for( auto& [pos, vias] : viaMap )
2561 {
2562 for( PCB_VIA* via : vias )
2563 {
2564 if( !( via->GetLayerSet() & layerSetCu ).any() || !better( via ) )
2565 continue;
2566
2567 for( PCB_LAYER_ID layer : LSET( via->GetLayerSet() & layerSetCu ).CuStack() )
2568 {
2569 int radius = via->GetWidth( layer ) / 2;
2570 int64_t radiusSq = static_cast<int64_t>( radius ) * radius;
2571
2572 if( ( pt - pos ).SquaredEuclideanNorm() <= radiusSq )
2573 {
2574 hitVia = via;
2575 break;
2576 }
2577 }
2578
2579 if( settled() )
2580 break;
2581 }
2582
2583 // The walk reaches the next hop by re-opening this position, so there is
2584 // no reason to keep scanning once something covers the point.
2585 if( hitVia )
2586 break;
2587 }
2588 }
2589
2590 auto padIt = padMap.find( pt );
2591
2592 bool gotVia = hitVia != nullptr;
2593 bool gotPad = padIt != padMap.end() && ( padIt->second->GetLayerSet() & layerSetCu ).any();
2594 bool gotNonStartPad = gotPad && ( startPadSet.find( padIt->second ) == startPadSet.end() );
2595
2596 if( gotPad && !itemPassesFilter( padIt->second, true ) )
2597 {
2598 activePts.erase( activePts.begin() + i );
2599 continue;
2600 }
2601
2602 if( gotVia && !itemPassesFilter( hitVia, true ) )
2603 {
2604 activePts.erase( activePts.begin() + i );
2605 continue;
2606 }
2607
2608 if( aStopCondition == STOP_AT_JUNCTION )
2609 {
2610 size_t pt_count = 0;
2611
2612 for( PCB_TRACK* track : trackMap[pt] )
2613 {
2614 if( track->GetStart() != track->GetEnd() && layerSetCu.Contains( track->GetLayer() ) )
2615 pt_count++;
2616 }
2617
2618 if( pt_count > 2 || gotVia || gotNonStartPad )
2619 {
2620 activePts.erase( activePts.begin() + i );
2621 continue;
2622 }
2623 }
2624 else if( aStopCondition == STOP_AT_PAD )
2625 {
2626 if( gotNonStartPad )
2627 {
2628 activePts.erase( activePts.begin() + i );
2629 continue;
2630 }
2631 }
2632
2633 if( gotPad )
2634 {
2635 PAD* pad = padIt->second;
2636
2637 if( !pad->HasFlag( SKIP_STRUCT ) )
2638 {
2639 pad->SetFlags( SKIP_STRUCT );
2640 cleanupItems.push_back( pad );
2641
2642 activePts.push_back( { pad->GetPosition(), pad->GetLayerSet() } );
2643 expand = true;
2644 }
2645 }
2646
2647 for( PCB_TRACK* track : trackMap[pt] )
2648 {
2649 if( !layerSetCu.Contains( track->GetLayer() ) )
2650 continue;
2651
2652 if( !itemPassesFilter( track, true ) )
2653 continue;
2654
2655 if( !track->IsSelected() && inScope( track ) )
2656 select( track );
2657
2658 if( !track->HasFlag( SKIP_STRUCT ) )
2659 {
2660 track->SetFlags( SKIP_STRUCT );
2661 cleanupItems.push_back( track );
2662
2663 if( track->GetStart() == pt )
2664 activePts.push_back( { track->GetEnd(), track->GetLayerSet() } );
2665 else
2666 activePts.push_back( { track->GetStart(), track->GetLayerSet() } );
2667
2668 if( aStopCondition != STOP_AT_SEGMENT )
2669 expand = true;
2670 }
2671 }
2672
2673 for( PCB_SHAPE* shape : shapeMap[pt] )
2674 {
2675 if( !layerSetCu.Contains( shape->GetLayer() ) )
2676 continue;
2677
2678 if( !itemPassesFilter( shape, true ) )
2679 continue;
2680
2681 if( !shape->IsSelected() && inScope( shape ) )
2682 select( shape );
2683
2684 if( !shape->HasFlag( SKIP_STRUCT ) )
2685 {
2686 shape->SetFlags( SKIP_STRUCT );
2687 cleanupItems.push_back( shape );
2688
2689 for( const VECTOR2I& newPoint : shape->GetConnectionPoints() )
2690 {
2691 if( newPoint == pt )
2692 continue;
2693
2694 activePts.push_back( { newPoint, shape->GetLayerSet() } );
2695 }
2696
2697 if( aStopCondition != STOP_AT_SEGMENT )
2698 expand = true;
2699 }
2700 }
2701
2702 if( hitVia )
2703 {
2704 if( !hitVia->IsSelected() && inScope( hitVia ) )
2705 select( hitVia );
2706
2707 if( !hitVia->HasFlag( SKIP_STRUCT ) )
2708 {
2709 hitVia->SetFlags( SKIP_STRUCT );
2710 cleanupItems.push_back( hitVia );
2711
2712 VECTOR2I viaPos = hitVia->GetPosition();
2713
2714 int maxRadius = 0;
2715
2716 for( PCB_LAYER_ID layer : hitVia->GetLayerSet().CuStack() )
2717 maxRadius = std::max( maxRadius, hitVia->GetWidth( layer ) / 2 );
2718
2719 int64_t maxRadiusSq = static_cast<int64_t>( maxRadius ) * maxRadius;
2720
2721 for( auto& [trkPt, tracks] : trackMap )
2722 {
2723 if( ( trkPt - viaPos ).SquaredEuclideanNorm() > maxRadiusSq )
2724 continue;
2725
2726 // Verify point is inside the VIA pad on at least one track layer
2727 bool inside = false;
2728
2729 for( PCB_TRACK* trk : tracks )
2730 {
2731 PCB_LAYER_ID trkLayer = trk->GetLayer();
2732
2733 if( !hitVia->GetLayerSet().Contains( trkLayer ) )
2734 continue;
2735
2736 int r = hitVia->GetWidth( trkLayer ) / 2;
2737 int64_t rSq = static_cast<int64_t>( r ) * r;
2738
2739 if( ( trkPt - viaPos ).SquaredEuclideanNorm() <= rSq )
2740 {
2741 inside = true;
2742 break;
2743 }
2744 }
2745
2746 if( inside )
2747 activePts.push_back( { trkPt, hitVia->GetLayerSet() } );
2748 }
2749
2750 // Re-open this position so the next hop of a stack is found.
2751 activePts.push_back( { viaPos, hitVia->GetLayerSet() } );
2752
2753 if( aStopCondition != STOP_AT_SEGMENT )
2754 expand = true;
2755 }
2756 }
2757
2758 activePts.erase( activePts.begin() + i );
2759 }
2760
2761 // Refresh display for the feel of progress
2762 if( refreshTimer.msecs() >= refreshIntervalMs )
2763 {
2764 if( m_selection.Size() != lastSelectionSize )
2765 {
2766 m_frame->GetCanvas()->ForceRefresh();
2767 lastSelectionSize = m_selection.Size();
2768 }
2769
2770 refreshTimer.Start();
2771 }
2772 }
2773 }
2774
2775 std::set<EDA_ITEM*> toDeselect;
2776 std::set<EDA_ITEM*> toSelect;
2777
2778 // Promote generated members to their PCB_GENERATOR parents. Generators that mark
2779 // their children as individually selectable are exempt.
2780 for( EDA_ITEM* item : m_selection )
2781 {
2782 if( !item->IsBOARD_ITEM() )
2783 continue;
2784
2785 BOARD_ITEM* boardItem = static_cast<BOARD_ITEM*>( item );
2786 EDA_GROUP* parent = boardItem->GetParentGroup();
2787
2788 if( parent && parent->AsEdaItem()->Type() == PCB_GENERATOR_T )
2789 {
2790 PCB_GENERATOR* gen = static_cast<PCB_GENERATOR*>( parent->AsEdaItem() );
2791
2793 continue;
2794
2795 toDeselect.insert( item );
2796
2797 if( !parent->AsEdaItem()->IsSelected() )
2798 toSelect.insert( parent->AsEdaItem() );
2799 }
2800 }
2801
2802 for( EDA_ITEM* item : toDeselect )
2803 unselect( item );
2804
2805 for( EDA_ITEM* item : toSelect )
2806 select( item );
2807
2808 for( BOARD_CONNECTED_ITEM* item : cleanupItems )
2809 item->ClearFlags( SKIP_STRUCT );
2810}
2811
2812
2814{
2815 if( aItem->Type() == PCB_SHAPE_T )
2816 {
2817 const PCB_SHAPE* shape = static_cast<const PCB_SHAPE*>( aItem );
2818
2819 switch( shape->GetShape() )
2820 {
2821 case SHAPE_T::SEGMENT:
2822 case SHAPE_T::ARC:
2823 case SHAPE_T::BEZIER:
2824 return !shape->IsOnCopperLayer();
2825
2826 case SHAPE_T::POLY:
2827 return !shape->IsOnCopperLayer() && !shape->IsClosed();
2828
2829 default:
2830 return false;
2831 }
2832 }
2833
2834 return false;
2835}
2836
2837
2838void PCB_SELECTION_TOOL::selectAllConnectedShapes( const std::vector<PCB_SHAPE*>& aStartItems )
2839{
2840 std::stack<PCB_SHAPE*> toSearch;
2841 std::set<PCB_SHAPE*> toCleanup;
2842
2843 for( PCB_SHAPE* startItem : aStartItems )
2844 toSearch.push( startItem );
2845
2846 GENERAL_COLLECTOR collector;
2848
2849 auto searchPoint =
2850 [&]( const VECTOR2I& aWhere )
2851 {
2852 collector.Collect( board(), { PCB_SHAPE_T }, aWhere, guide );
2853
2854 for( EDA_ITEM* item : collector )
2855 {
2856 if( isExpandableGraphicShape( item ) )
2857 toSearch.push( static_cast<PCB_SHAPE*>( item ) );
2858 }
2859 };
2860
2861 while( !toSearch.empty() )
2862 {
2863 PCB_SHAPE* shape = toSearch.top();
2864 toSearch.pop();
2865
2866 if( shape->HasFlag( SKIP_STRUCT ) )
2867 continue;
2868
2869 shape->SetFlags( SKIP_STRUCT );
2870 toCleanup.insert( shape );
2871
2872 if( !itemPassesFilter( shape, true ) )
2873 continue;
2874
2875 select( shape );
2876 guide.SetLayerVisibleBits( shape->GetLayerSet() );
2877
2878 searchPoint( shape->GetStart() );
2879 searchPoint( shape->GetEnd() );
2880 }
2881
2882 for( PCB_SHAPE* shape : toCleanup )
2883 shape->ClearFlags( SKIP_STRUCT );
2884}
2885
2886
2888{
2889 // Get all pads
2890 std::vector<PAD*> pads;
2891
2892 for( EDA_ITEM* item : m_selection.GetItems() )
2893 {
2894 if( item->Type() == PCB_FOOTPRINT_T )
2895 {
2896 for( PAD* pad : static_cast<FOOTPRINT*>( item )->Pads() )
2897 pads.push_back( pad );
2898 }
2899 else if( item->Type() == PCB_PAD_T )
2900 {
2901 pads.push_back( static_cast<PAD*>( item ) );
2902 }
2903 }
2904
2905 // Select every footprint on the end of the ratsnest for each pad in our selection
2906 std::shared_ptr<CONNECTIVITY_DATA> conn = board()->GetConnectivity();
2907
2908 for( PAD* pad : pads )
2909 {
2910 for( const CN_EDGE& edge : conn->GetRatsnestForPad( pad ) )
2911 {
2912 wxCHECK2( edge.GetSourceNode() && !edge.GetSourceNode()->Dirty(), continue );
2913 wxCHECK2( edge.GetTargetNode() && !edge.GetTargetNode()->Dirty(), continue );
2914
2915 BOARD_CONNECTED_ITEM* sourceParent = edge.GetSourceNode()->Parent();
2916 BOARD_CONNECTED_ITEM* targetParent = edge.GetTargetNode()->Parent();
2917
2918 if( sourceParent == pad )
2919 {
2920 if( targetParent->Type() == PCB_PAD_T )
2921 select( static_cast<PAD*>( targetParent )->GetParent() );
2922 }
2923 else if( targetParent == pad )
2924 {
2925 if( sourceParent->Type() == PCB_PAD_T )
2926 select( static_cast<PAD*>( sourceParent )->GetParent() );
2927 }
2928 }
2929 }
2930
2931 return 0;
2932}
2933
2934
2936{
2937 // Get all connected items that can represent the source side of the ratsnest.
2938 std::vector<BOARD_CONNECTED_ITEM*> sourceItems;
2939 std::vector<FOOTPRINT*> nearestFootprints;
2940
2941 for( EDA_ITEM* item : m_selection.GetItems() )
2942 {
2943 if( item->Type() == PCB_FOOTPRINT_T )
2944 {
2945 for( PAD* pad : static_cast<FOOTPRINT*>( item )->Pads() )
2946 sourceItems.push_back( pad );
2947 }
2948 else if( BOARD_CONNECTED_ITEM* connItem = dynamic_cast<BOARD_CONNECTED_ITEM*>( item ) )
2949 {
2950 sourceItems.push_back( connItem );
2951 }
2952 }
2953
2954 std::shared_ptr<CONNECTIVITY_DATA> conn = board()->GetConnectivity();
2955 std::shared_ptr<CN_CONNECTIVITY_ALGO> connAlgo = conn->GetConnectivityAlgo();
2956
2957 for( BOARD_CONNECTED_ITEM* sourceItem : sourceItems )
2958 {
2959 RN_NET* net = conn->GetRatsnestForNet( sourceItem->GetNetCode() );
2960
2961 // Need to have something unconnected to grab
2962 if( !net || net->GetEdges().empty() || !connAlgo->ItemExists( sourceItem ) )
2963 continue;
2964
2965 std::vector<std::shared_ptr<CN_CLUSTER>> sourceClusters;
2966
2967 for( CN_ITEM* cnItem : connAlgo->ItemEntry( sourceItem ).GetItems() )
2968 {
2969 for( const std::shared_ptr<CN_ANCHOR>& anchor : cnItem->Anchors() )
2970 {
2971 if( anchor->GetCluster() )
2972 sourceClusters.push_back( anchor->GetCluster() );
2973 }
2974 }
2975
2976 if( sourceClusters.empty() )
2977 continue;
2978
2979 auto isSourceAnchor =
2980 [&]( const std::shared_ptr<const CN_ANCHOR>& aAnchor )
2981 {
2982 if( aAnchor->Parent() == sourceItem )
2983 return true;
2984
2985 return std::find( sourceClusters.begin(), sourceClusters.end(),
2986 aAnchor->GetCluster() ) != sourceClusters.end();
2987 };
2988
2989 double currentDistance = DBL_MAX;
2990 FOOTPRINT* nearest = nullptr;
2991
2992 // Check every ratsnest line for the nearest one
2993 for( const CN_EDGE& edge : net->GetEdges() )
2994 {
2995 const std::shared_ptr<const CN_ANCHOR>& source = edge.GetSourceNode();
2996 const std::shared_ptr<const CN_ANCHOR>& target = edge.GetTargetNode();
2997
2998 wxCHECK2( source && !source->Dirty() && target && !target->Dirty(), continue );
2999
3000 if( source->Parent()->GetParentFootprint() == target->Parent()->GetParentFootprint() )
3001 {
3002 continue; // This edge is a loop on the same footprint
3003 }
3004
3005 bool sourceMatches = isSourceAnchor( source );
3006 bool targetMatches = isSourceAnchor( target );
3007
3008 if( sourceMatches == targetMatches )
3009 continue;
3010
3011 const CN_ANCHOR* other = sourceMatches ? target.get() : source.get();
3012
3013 // We only want to grab footprints, so the ratnest has to point to a pad
3014 if( other->Parent()->Type() != PCB_PAD_T )
3015 continue;
3016
3017 if( edge.GetLength() < currentDistance )
3018 {
3019 currentDistance = edge.GetLength();
3020 nearest = other->Parent()->GetParentFootprint();
3021 }
3022 }
3023
3024 if( nearest != nullptr )
3025 nearestFootprints.push_back( nearest );
3026 }
3027
3028 if( nearestFootprints.empty() )
3029 return 0;
3030
3032
3033 for( FOOTPRINT* footprint : nearestFootprints )
3034 select( footprint );
3035
3037
3038 return 0;
3039}
3040
3041
3042void PCB_SELECTION_TOOL::SelectAllItemsOnNet( int aNetCode, bool aSelect )
3043{
3044 std::shared_ptr<CONNECTIVITY_DATA> conn = board()->GetConnectivity();
3045
3046 for( BOARD_ITEM* item : conn->GetNetItems( aNetCode, { PCB_TRACE_T,
3047 PCB_ARC_T,
3048 PCB_VIA_T,
3049 PCB_SHAPE_T } ) )
3050 {
3051 if( itemPassesFilter( item, true, nullptr ) )
3052 aSelect ? select( item ) : unselect( item );
3053 }
3054}
3055
3056
3058{
3059 bool select = aEvent.IsAction( &PCB_ACTIONS::selectNet );
3060
3061 // If we've been passed an argument, just select that netcode1
3062 int netcode = aEvent.Parameter<int>();
3063
3064 if( netcode > 0 )
3065 {
3066 SelectAllItemsOnNet( netcode, select );
3067
3068 // Inform other potentially interested tools
3069 if( m_selection.Size() > 0 )
3070 m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
3071 else
3072 m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent );
3073
3074 return 0;
3075 }
3076
3077 if( !selectCursor() )
3078 return 0;
3079
3080 // copy the selection, since we're going to iterate and modify
3081 auto selection = m_selection.GetItems();
3082
3083 for( EDA_ITEM* i : selection )
3084 {
3085 BOARD_CONNECTED_ITEM* connItem = dynamic_cast<BOARD_CONNECTED_ITEM*>( i );
3086
3087 if( connItem )
3088 SelectAllItemsOnNet( connItem->GetNetCode(), select );
3089 }
3090
3091 // Inform other potentially interested tools
3092 if( m_selection.Size() > 0 )
3093 m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
3094 else
3095 m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent );
3096
3097 return 0;
3098}
3099
3100
3102{
3103 if( !selectCursor() )
3104 return 0;
3105
3106 auto selection = m_selection.GetItems();
3107
3108 for( EDA_ITEM* i : selection )
3109 {
3110 BOARD_CONNECTED_ITEM* connItem = dynamic_cast<BOARD_CONNECTED_ITEM*>( i );
3111
3112 if( !connItem )
3113 continue;
3114
3115 NETINFO_ITEM* netInfo = connItem->GetNet();
3116
3117 if( !netInfo )
3118 continue;
3119
3120 const wxString& chainName = netInfo->GetNetChain();
3121
3122 if( chainName.IsEmpty() )
3123 {
3124 // Net is not part of any chain; fall back to single-net behaviour.
3125 SelectAllItemsOnNet( connItem->GetNetCode(), true );
3126 continue;
3127 }
3128
3129 for( NETINFO_ITEM* candidate : board()->GetNetInfo() )
3130 {
3131 if( candidate && candidate->GetNetChain() == chainName )
3132 SelectAllItemsOnNet( candidate->GetNetCode(), true );
3133 }
3134 }
3135
3136 if( m_selection.Size() > 0 )
3137 m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
3138 else
3139 m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent );
3140
3141 return 0;
3142}
3143
3144
3146{
3147 std::vector<BOARD_ITEM*> footprints;
3148
3149 // store all footprints that are on that sheet path
3150 for( FOOTPRINT* footprint : board()->Footprints() )
3151 {
3152 if( footprint == nullptr )
3153 continue;
3154
3155 wxString footprint_path = footprint->GetPath().AsString().BeforeLast( '/' );
3156
3157 if( footprint_path.IsEmpty() )
3158 footprint_path += '/';
3159
3160 if( footprint_path == aSheetPath )
3161 footprints.push_back( footprint );
3162 }
3163
3164 for( BOARD_ITEM* i : footprints )
3165 {
3166 if( i != nullptr )
3167 select( i );
3168 }
3169
3170 selectConnections( footprints );
3171}
3172
3173
3174void PCB_SELECTION_TOOL::selectConnections( const std::vector<BOARD_ITEM*>& aItems )
3175{
3176 // Generate a list of all pads, and of all nets they belong to.
3177 std::list<int> netcodeList;
3178 std::vector<BOARD_CONNECTED_ITEM*> padList;
3179
3180 for( BOARD_ITEM* item : aItems )
3181 {
3182 switch( item->Type() )
3183 {
3184 case PCB_FOOTPRINT_T:
3185 {
3186 for( PAD* pad : static_cast<FOOTPRINT*>( item )->Pads() )
3187 {
3188 if( pad->IsConnected() )
3189 {
3190 netcodeList.push_back( pad->GetNetCode() );
3191 padList.push_back( pad );
3192 }
3193 }
3194
3195 break;
3196 }
3197
3198 case PCB_PAD_T:
3199 {
3200 PAD* pad = static_cast<PAD*>( item );
3201
3202 if( pad->IsConnected() )
3203 {
3204 netcodeList.push_back( pad->GetNetCode() );
3205 padList.push_back( pad );
3206 }
3207
3208 break;
3209 }
3210
3211 default:
3212 break;
3213 }
3214 }
3215
3216 // Sort for binary search
3217 std::sort( padList.begin(), padList.end() );
3218
3219 // remove all duplicates
3220 netcodeList.sort();
3221 netcodeList.unique();
3222
3224
3225 // now we need to find all footprints that are connected to each of these nets then we need
3226 // to determine if these footprints are in the list of footprints
3227 std::vector<int> removeCodeList;
3228 std::shared_ptr<CONNECTIVITY_DATA> conn = board()->GetConnectivity();
3229
3230 for( int netCode : netcodeList )
3231 {
3232 for( BOARD_CONNECTED_ITEM* pad : conn->GetNetItems( netCode, { PCB_PAD_T } ) )
3233 {
3234 if( !std::binary_search( padList.begin(), padList.end(), pad ) )
3235 {
3236 // if we cannot find the pad in the padList then we can assume that that pad
3237 // should not be used, therefore invalidate this netcode.
3238 removeCodeList.push_back( netCode );
3239 break;
3240 }
3241 }
3242 }
3243
3244 for( int removeCode : removeCodeList )
3245 netcodeList.remove( removeCode );
3246
3247 std::unordered_set<BOARD_ITEM*> localConnectionList;
3248
3249 for( int netCode : netcodeList )
3250 {
3251 for( BOARD_ITEM* item : conn->GetNetItems( netCode, { PCB_TRACE_T,
3252 PCB_ARC_T,
3253 PCB_VIA_T,
3254 PCB_SHAPE_T } ) )
3255 {
3256 localConnectionList.insert( item );
3257 }
3258 }
3259
3260 for( BOARD_ITEM* item : localConnectionList )
3261 select( item );
3262}
3263
3264
3266{
3267 std::vector<BOARD_ITEM*>* items = aEvent.Parameter<std::vector<BOARD_ITEM*>*>();
3268
3269 if( items )
3270 doSyncSelection( *items, false );
3271
3272 return 0;
3273}
3274
3275
3277{
3278 std::vector<BOARD_ITEM*>* items = aEvent.Parameter<std::vector<BOARD_ITEM*>*>();
3279
3280 if( items )
3281 doSyncSelection( *items, true );
3282
3283 return 0;
3284}
3285
3286
3287void PCB_SELECTION_TOOL::doSyncSelection( const std::vector<BOARD_ITEM*>& aItems, bool aWithNets )
3288{
3289 if( m_selection.Front() && m_selection.Front()->IsMoving() )
3290 return;
3291
3292 // Also check the incoming items. If the cross-probe flash timer cleared the selection
3293 // during a move, Front() would be null but the items are still being actively moved.
3294 for( const BOARD_ITEM* item : aItems )
3295 {
3296 if( item->IsMoving() )
3297 return;
3298 }
3299
3300 ClearSelection( true /*quiet mode*/ );
3301
3302 // Perform individual selection of each item before processing the event.
3303 for( BOARD_ITEM* item : aItems )
3304 select( item );
3305
3306 if( aWithNets )
3307 selectConnections( aItems );
3308
3309 BOX2I bbox = m_selection.GetBoundingBox();
3310
3311 if( bbox.GetWidth() != 0 && bbox.GetHeight() != 0 )
3312 {
3313 if( m_frame->GetPcbNewSettings()->m_CrossProbing.center_on_items )
3314 {
3315 if( m_frame->GetPcbNewSettings()->m_CrossProbing.zoom_to_fit )
3316 ZoomFitCrossProbeBBox( bbox );
3317
3318 m_frame->FocusOnLocation( bbox.Centre() );
3319 }
3320 }
3321
3323
3324 m_frame->GetCanvas()->ForceRefresh();
3325
3326 if( m_selection.Size() > 0 )
3327 m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
3328}
3329
3330
3332{
3333 ClearSelection( true /*quiet mode*/ );
3334 wxString sheetPath = *aEvent.Parameter<wxString*>();
3335
3336 selectAllItemsOnSheet( sheetPath );
3337
3339
3340 if( m_selection.Size() > 0 )
3341 m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
3342
3343 return 0;
3344}
3345
3346
3348{
3349 // this function currently only supports footprints since they are only on one sheet.
3350 EDA_ITEM* item = m_selection.Front();
3351
3352 if( !item )
3353 return 0;
3354
3355 if( item->Type() != PCB_FOOTPRINT_T )
3356 return 0;
3357
3358 FOOTPRINT* footprint = dynamic_cast<FOOTPRINT*>( item );
3359
3360 if( !footprint || footprint->GetPath().empty() )
3361 return 0;
3362
3363 ClearSelection( true /*quiet mode*/ );
3364
3365 // get the sheet path only.
3366 wxString sheetPath = footprint->GetPath().AsString().BeforeLast( '/' );
3367
3368 if( sheetPath.IsEmpty() )
3369 sheetPath += '/';
3370
3371 selectAllItemsOnSheet( sheetPath );
3372
3373 // Inform other potentially interested tools
3374 if( m_selection.Size() > 0 )
3375 m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
3376
3377 return 0;
3378}
3379
3380
3382{
3383 // Should recalculate the view to zoom in on the selection.
3384 BOX2I selectionBox = m_selection.GetBoundingBox();
3386
3387 VECTOR2D screenSize = view->ToWorld( ToVECTOR2D( m_frame->GetCanvas()->GetClientSize() ),
3388 false );
3389 screenSize.x = std::max( 10.0, screenSize.x );
3390 screenSize.y = std::max( 10.0, screenSize.y );
3391
3392 if( selectionBox.GetWidth() != 0 || selectionBox.GetHeight() != 0 )
3393 {
3394 VECTOR2D vsize = selectionBox.GetSize();
3395 double scale = view->GetScale() / std::max( fabs( vsize.x / screenSize.x ),
3396 fabs( vsize.y / screenSize.y ) );
3397 view->SetScale( scale );
3398 view->SetCenter( selectionBox.Centre() );
3399 view->Add( &m_selection );
3400 }
3401
3402 m_frame->GetCanvas()->ForceRefresh();
3403}
3404
3405
3407{
3408 // Should recalculate the view to zoom in on the bbox.
3410
3411 if( aBBox.GetWidth() == 0 )
3412 return;
3413
3414 BOX2I bbox = aBBox;
3415 bbox.Normalize();
3416
3417 //#define DEFAULT_PCBNEW_CODE // Un-comment for normal full zoom KiCad algorithm
3418#ifdef DEFAULT_PCBNEW_CODE
3419 auto bbSize = bbox.Inflate( bbox.GetWidth() * 0.2f ).GetSize();
3420 auto screenSize = view->ToWorld( GetCanvas()->GetClientSize(), false );
3421
3422 // The "fabs" on x ensures the right answer when the view is flipped
3423 screenSize.x = std::max( 10.0, fabs( screenSize.x ) );
3424 screenSize.y = std::max( 10.0, screenSize.y );
3425 double ratio = std::max( fabs( bbSize.x / screenSize.x ), fabs( bbSize.y / screenSize.y ) );
3426
3427 // Try not to zoom on every cross-probe; it gets very noisy
3428 if( crossProbingSettings.zoom_to_fit && ( ratio < 0.5 || ratio > 1.0 ) )
3429 view->SetScale( view->GetScale() / ratio );
3430#endif // DEFAULT_PCBNEW_CODE
3431
3432#ifndef DEFAULT_PCBNEW_CODE // Do the scaled zoom
3433 auto bbSize = bbox.Inflate( KiROUND( bbox.GetWidth() * 0.2 ) ).GetSize();
3434 VECTOR2D screenSize = view->ToWorld( ToVECTOR2D( m_frame->GetCanvas()->GetClientSize() ), false );
3435
3436 // This code tries to come up with a zoom factor that doesn't simply zoom in
3437 // to the cross probed component, but instead shows a reasonable amount of the
3438 // circuit around it to provide context. This reduces or eliminates the need
3439 // to manually change the zoom because it's too close.
3440
3441 // Using the default text height as a constant to compare against, use the
3442 // height of the bounding box of visible items for a footprint to figure out
3443 // if this is a big footprint (like a processor) or a small footprint (like a resistor).
3444 // This ratio is not useful by itself as a scaling factor. It must be "bent" to
3445 // provide good scaling at varying component sizes. Bigger components need less
3446 // scaling than small ones.
3447 double currTextHeight = pcbIUScale.mmToIU( DEFAULT_TEXT_SIZE );
3448
3449 double compRatio = bbSize.y / currTextHeight; // Ratio of component to text height
3450
3451 // This will end up as the scaling factor we apply to "ratio".
3452 double compRatioBent = 1.0;
3453
3454 // This is similar to the original KiCad code that scaled the zoom to make sure
3455 // components were visible on screen. It's simply a ratio of screen size to
3456 // component size, and its job is to zoom in to make the component fullscreen.
3457 // Earlier in the code the component BBox is given a 20% margin to add some
3458 // breathing room. We compare the height of this enlarged component bbox to the
3459 // default text height. If a component will end up with the sides clipped, we
3460 // adjust later to make sure it fits on screen.
3461 //
3462 // The "fabs" on x ensures the right answer when the view is flipped
3463 screenSize.x = std::max( 10.0, fabs( screenSize.x ) );
3464 screenSize.y = std::max( 10.0, screenSize.y );
3465 double ratio = std::max( -1.0, fabs( bbSize.y / screenSize.y ) );
3466
3467 // Original KiCad code for how much to scale the zoom
3468 double kicadRatio = std::max( fabs( bbSize.x / screenSize.x ),
3469 fabs( bbSize.y / screenSize.y ) );
3470
3471 // LUT to scale zoom ratio to provide reasonable schematic context. Must work
3472 // with footprints of varying sizes (e.g. 0402 package and 200 pin BGA).
3473 // "first" is used as the input and "second" as the output
3474 //
3475 // "first" = compRatio (footprint height / default text height)
3476 // "second" = Amount to scale ratio by
3477 std::vector<std::pair<double, double>> lut {
3478 { 1, 8 },
3479 { 1.5, 5 },
3480 { 3, 3 },
3481 { 4.5, 2.5 },
3482 { 8, 2.0 },
3483 { 12, 1.7 },
3484 { 16, 1.5 },
3485 { 24, 1.3 },
3486 { 32, 1.0 },
3487 };
3488
3489
3490 std::vector<std::pair<double, double>>::iterator it;
3491
3492 compRatioBent = lut.back().second; // Large component default
3493
3494 if( compRatio >= lut.front().first )
3495 {
3496 // Use LUT to do linear interpolation of "compRatio" within "first", then
3497 // use that result to linearly interpolate "second" which gives the scaling
3498 // factor needed.
3499
3500 for( it = lut.begin(); it < lut.end() - 1; it++ )
3501 {
3502 if( it->first <= compRatio && next( it )->first >= compRatio )
3503 {
3504 double diffx = compRatio - it->first;
3505 double diffn = next( it )->first - it->first;
3506
3507 compRatioBent = it->second + ( next( it )->second - it->second ) * diffx / diffn;
3508 break; // We have our interpolated value
3509 }
3510 }
3511 }
3512 else
3513 {
3514 compRatioBent = lut.front().second; // Small component default
3515 }
3516
3517 // If the width of the part we're probing is bigger than what the screen width will be
3518 // after the zoom, then punt and use the KiCad zoom algorithm since it guarantees the
3519 // part's width will be encompassed within the screen. This will apply to parts that
3520 // are much wider than they are tall.
3521
3522 if( bbSize.x > screenSize.x * ratio * compRatioBent )
3523 {
3524 // Use standard KiCad zoom algorithm for parts too wide to fit screen/
3525 ratio = kicadRatio;
3526 compRatioBent = 1.0; // Reset so we don't modify the "KiCad" ratio
3527 wxLogTrace( "CROSS_PROBE_SCALE", "Part TOO WIDE for screen. Using normal KiCad zoom ratio: %1.5f", ratio );
3528 }
3529
3530 // Now that "compRatioBent" holds our final scaling factor we apply it to the original
3531 // fullscreen zoom ratio to arrive at the final ratio itself.
3532 ratio *= compRatioBent;
3533
3534 bool alwaysZoom = false; // DEBUG - allows us to minimize zooming or not
3535
3536 // Try not to zoom on every cross-probe; it gets very noisy
3537 if( ( ratio < 0.5 || ratio > 1.0 ) || alwaysZoom )
3538 view->SetScale( view->GetScale() / ratio );
3539#endif // ifndef DEFAULT_PCBNEW_CODE
3540}
3541
3542
3544{
3545 bool cleared = false;
3546
3547 if( m_selection.GetSize() > 0 )
3548 {
3549 // Don't fire an event now; most of the time it will be redundant as we're about to
3550 // fire a SelectedEvent.
3551 cleared = true;
3552 ClearSelection( true /*quiet mode*/ );
3553 }
3554
3555 if( aItem )
3556 {
3557 switch( aItem->Type() )
3558 {
3559 case PCB_NETINFO_T:
3560 {
3561 int netCode = static_cast<NETINFO_ITEM*>( aItem )->GetNetCode();
3562
3563 if( netCode > 0 )
3564 {
3565 SelectAllItemsOnNet( netCode, true );
3566 m_frame->FocusOnLocation( aItem->GetCenter() );
3567 }
3568 break;
3569 }
3570
3571 default:
3572 select( aItem );
3573 m_frame->FocusOnLocation( aItem->GetPosition() );
3574 }
3575
3576 // If the item has a bounding box, then zoom out if needed
3577 if( aItem->GetBoundingBox().GetHeight() > 0 && aItem->GetBoundingBox().GetWidth() > 0 )
3578 {
3579 // This adds some margin
3580 double marginFactor = 2;
3581
3582 KIGFX::PCB_VIEW* pcbView = canvas()->GetView();
3583 BOX2D screenBox = pcbView->GetViewport();
3584 VECTOR2D screenSize = screenBox.GetSize();
3585 BOX2I screenRect = BOX2ISafe( screenBox.GetOrigin(), screenSize / marginFactor );
3586
3587 if( !screenRect.Contains( aItem->GetBoundingBox() ) )
3588 {
3589 double scaleX = screenSize.x / static_cast<double>( aItem->GetBoundingBox().GetWidth() );
3590 double scaleY = screenSize.y / static_cast<double>( aItem->GetBoundingBox().GetHeight() );
3591
3592 scaleX /= marginFactor;
3593 scaleY /= marginFactor;
3594
3595 double scale = scaleX > scaleY ? scaleY : scaleX;
3596
3597 if( scale < 1 ) // Don't zoom in, only zoom out
3598 {
3599 pcbView->SetScale( pcbView->GetScale() * ( scale ) );
3600
3601 //Let's refocus because there is an algorithm to avoid dialogs in there.
3602 m_frame->FocusOnLocation( aItem->GetCenter() );
3603 }
3604 }
3605 }
3606 // Inform other potentially interested tools
3607 m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
3608 }
3609 else if( cleared )
3610 {
3611 m_toolMgr->ProcessEvent( EVENTS::ClearedEvent );
3612 }
3613
3614 m_frame->GetCanvas()->ForceRefresh();
3615}
3616
3617
3623static bool itemIsIncludedByFilter( const BOARD_ITEM& aItem, const BOARD& aBoard,
3624 const DIALOG_FILTER_SELECTION::OPTIONS& aFilterOptions )
3625{
3626 switch( aItem.Type() )
3627 {
3628 case PCB_FOOTPRINT_T:
3629 {
3630 const FOOTPRINT& footprint = static_cast<const FOOTPRINT&>( aItem );
3631
3632 return aFilterOptions.includeFootprints && ( aFilterOptions.includeLockedFootprints
3633 || !footprint.IsLocked() );
3634 }
3635
3636 case PCB_TRACE_T:
3637 case PCB_ARC_T:
3638 return aFilterOptions.includeTracks;
3639
3640 case PCB_VIA_T:
3641 return aFilterOptions.includeVias;
3642
3643 case PCB_ZONE_T:
3644 return aFilterOptions.includeZones;
3645
3646 case PCB_SHAPE_T:
3647 case PCB_TARGET_T:
3648 case PCB_DIM_ALIGNED_T:
3649 case PCB_DIM_CENTER_T:
3650 case PCB_DIM_RADIAL_T:
3652 case PCB_DIM_LEADER_T:
3653 if( aItem.GetLayer() == Edge_Cuts )
3654 return aFilterOptions.includeBoardOutlineLayer;
3655 else
3656 return aFilterOptions.includeItemsOnTechLayers;
3657
3658 case PCB_GRID_ITEM_T:
3659 return aFilterOptions.includeItemsOnTechLayers;
3660
3661 case PCB_FIELD_T:
3662 case PCB_TEXT_T:
3663 case PCB_TEXTBOX_T:
3664 case PCB_TABLE_T:
3665 case PCB_DRILL_CHART_T:
3666 case PCB_DRILL_MAP_T:
3667 case PCB_TABLECELL_T:
3668 return aFilterOptions.includePcbTexts;
3669
3670 default:
3671 // Filter dialog is inclusive, not exclusive. If it's not included, then it doesn't
3672 // get selected.
3673 return false;
3674 }
3675}
3676
3677
3679{
3680 const BOARD& board = *getModel<BOARD>();
3681 DIALOG_FILTER_SELECTION::OPTIONS& opts = m_priv->m_filterOpts;
3682 DIALOG_FILTER_SELECTION dlg( m_frame, opts );
3683
3684 const int cmd = dlg.ShowModal();
3685
3686 if( cmd != wxID_OK )
3687 return 0;
3688
3689 // copy current selection
3690 std::deque<EDA_ITEM*> selection = m_selection.GetItems();
3691
3692 ClearSelection( true /*quiet mode*/ );
3693
3694 // re-select items from the saved selection according to the dialog options
3695 for( EDA_ITEM* i : selection )
3696 {
3697 if( !i->IsBOARD_ITEM() )
3698 continue;
3699
3700 BOARD_ITEM* item = static_cast<BOARD_ITEM*>( i );
3701 bool include = itemIsIncludedByFilter( *item, board, opts );
3702
3703 if( include )
3704 select( item );
3705 }
3706
3707 m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
3708
3709 return 0;
3710}
3711
3712
3714 PCB_SELECTION_FILTER_OPTIONS* aRejected )
3715{
3716 if( aCollector.GetCount() == 0 )
3717 return;
3718
3719 std::set<BOARD_ITEM*> rejected;
3720
3721 for( EDA_ITEM* i : aCollector )
3722 {
3723 if( !i->IsBOARD_ITEM() )
3724 continue;
3725
3726 BOARD_ITEM* item = static_cast<BOARD_ITEM*>( i );
3727
3728 if( !itemPassesFilter( item, aMultiSelect, aRejected ) )
3729 rejected.insert( item );
3730 }
3731
3732 for( BOARD_ITEM* item : rejected )
3733 aCollector.Remove( item );
3734}
3735
3736
3737bool PCB_SELECTION_TOOL::itemPassesFilter( BOARD_ITEM* aItem, bool aMultiSelect,
3738 PCB_SELECTION_FILTER_OPTIONS* aRejected )
3739{
3740 if( !m_filter.lockedItems )
3741 {
3742 if( aItem->IsLocked() || ( aItem->GetParent() && aItem->GetParent()->IsLocked() ) )
3743 {
3744 if( aItem->Type() == PCB_PAD_T && !aMultiSelect )
3745 {
3746 // allow a single pad to be selected -- there are a lot of operations that
3747 // require this so we allow this one inconsistency
3748 }
3749 else
3750 {
3751 if( aRejected )
3752 aRejected->lockedItems = true;
3753 return false;
3754 }
3755 }
3756 }
3757
3758 if( !aItem )
3759 return false;
3760
3761 KICAD_T itemType = aItem->Type();
3762
3763 if( itemType == PCB_GENERATOR_T )
3764 {
3765 if( static_cast<PCB_GENERATOR*>( aItem )->GetItems().empty() )
3766 {
3767 if( !m_filter.otherItems )
3768 {
3769 if( aRejected )
3770 aRejected->otherItems = true;
3771
3772 return false;
3773 }
3774 }
3775 else
3776 {
3777 itemType = ( *static_cast<PCB_GENERATOR*>( aItem )->GetItems().begin() )->Type();
3778 }
3779 }
3780
3781 switch( itemType )
3782 {
3783 case PCB_FOOTPRINT_T:
3784 if( !m_filter.footprints )
3785 {
3786 if( aRejected )
3787 aRejected->footprints = true;
3788
3789 return false;
3790 }
3791
3792 break;
3793
3794 case PCB_PAD_T:
3795 if( !m_filter.pads )
3796 {
3797 if( aRejected )
3798 aRejected->pads = true;
3799
3800 return false;
3801 }
3802
3803 break;
3804
3805 case PCB_TRACE_T:
3806 case PCB_ARC_T:
3807 if( !m_filter.tracks )
3808 {
3809 if( aRejected )
3810 aRejected->tracks = true;
3811
3812 return false;
3813 }
3814
3815 break;
3816
3817 case PCB_VIA_T:
3818 if( !m_filter.vias )
3819 {
3820 if( aRejected )
3821 aRejected->vias = true;
3822
3823 return false;
3824 }
3825
3826 break;
3827
3828 case PCB_ZONE_T:
3829 {
3830 ZONE* zone = static_cast<ZONE*>( aItem );
3831
3832 if( ( !m_filter.zones && !zone->GetIsRuleArea() )
3833 || ( !m_filter.keepouts && zone->GetIsRuleArea() ) )
3834 {
3835 if( aRejected )
3836 {
3837 if( zone->GetIsRuleArea() )
3838 aRejected->keepouts = true;
3839 else
3840 aRejected->zones = true;
3841 }
3842
3843 return false;
3844 }
3845
3846 // m_SolderMaskBridges zone is a special zone, only used to showsolder mask briges
3847 // after running DRC. it is not really a board item.
3848 // Never select it or delete by a Commit.
3849 if( zone == m_frame->GetBoard()->m_SolderMaskBridges )
3850 return false;
3851
3852 break;
3853 }
3854
3855 case PCB_SHAPE_T:
3856 case PCB_TARGET_T:
3857 if( !m_filter.graphics )
3858 {
3859 if( aRejected )
3860 aRejected->graphics = true;
3861
3862 return false;
3863 }
3864
3865 break;
3866
3868 if( !m_filter.graphics )
3869 {
3870 if( aRejected )
3871 aRejected->graphics = true;
3872
3873 return false;
3874 }
3875
3876 // a reference image living in a footprint must not be selected inside the board editor
3877 if( !m_isFootprintEditor && aItem->GetParentFootprint() )
3878 {
3879 if( aRejected )
3880 aRejected->text = true;
3881
3882 return false;
3883 }
3884
3885 break;
3886
3887 case PCB_FIELD_T:
3888 case PCB_TEXT_T:
3889 case PCB_TEXTBOX_T:
3890 case PCB_TABLE_T:
3891 case PCB_DRILL_CHART_T:
3892 case PCB_TABLECELL_T:
3893 if( !m_filter.text )
3894 return false;
3895
3896 break;
3897
3898 case PCB_DIM_ALIGNED_T:
3899 case PCB_DIM_CENTER_T:
3900 case PCB_DIM_RADIAL_T:
3902 case PCB_DIM_LEADER_T:
3903 if( !m_filter.dimensions )
3904 {
3905 if( aRejected )
3906 aRejected->dimensions = true;
3907
3908 return false;
3909 }
3910
3911 break;
3912
3913 case PCB_POINT_T:
3914 if( !m_filter.points )
3915 {
3916 if( aRejected )
3917 aRejected->points = true;
3918
3919 return false;
3920 }
3921
3922 break;
3923
3924 case PCB_GRID_ITEM_T:
3925 if( !m_filter.gridItems )
3926 {
3927 if( aRejected )
3928 aRejected->gridItems = true;
3929
3930 return false;
3931 }
3932
3933 break;
3934
3935 case PCB_BARCODE_T:
3936 default:
3937 if( !m_filter.otherItems )
3938 {
3939 if( aRejected )
3940 aRejected->otherItems = true;
3941
3942 return false;
3943 }
3944 }
3945
3946 return true;
3947}
3948
3949
3951{
3952 // Drop any table-cell range anchor along with the selection itself (do this even when the
3953 // selection is already empty so that the cached pointer is not left stranded)
3954 m_previousFirstCell = nullptr;
3955
3956 if( m_selection.Empty() )
3957 return;
3958
3959 while( m_selection.GetSize() )
3961
3962 view()->Update( &m_selection );
3963
3964 m_selection.SetIsHover( false );
3965 m_selection.ClearReferencePoint();
3966
3967 // Inform other potentially interested tools
3968 if( !aQuietMode )
3969 {
3970 m_toolMgr->ProcessEvent( EVENTS::ClearedEvent );
3972 }
3973}
3974
3975
3977{
3978 // The whole row goes in, because formatting one cell of a generated row and not its
3979 // neighbours is never what was meant
3980 if( m_selection.GetSize() != 1 || m_selection[0]->Type() != PCB_DRILL_CHART_T )
3981 return false;
3982
3983 PCB_DRILL_CHART* chart = static_cast<PCB_DRILL_CHART*>( m_selection[0] );
3984 PCB_TABLECELL* clicked = nullptr;
3985
3986 for( PCB_TABLECELL* cell : chart->GetCells() )
3987 {
3988 if( cell->HitTest( aPosition, 0 ) )
3989 {
3990 clicked = cell;
3991 break;
3992 }
3993 }
3994
3995 // The title and totals rows are configured from the chart's own dialog, so a click there
3996 // falls through to it rather than offering to reformat a row the user cannot vary
3997 if( !clicked || !chart->IsDataRow( clicked->GetRow() ) )
3998 return false;
3999
4000 ClearSelection( true );
4001
4002 for( PCB_TABLECELL* cell : chart->GetCells() )
4003 {
4004 if( cell->GetRow() == clicked->GetRow() )
4005 select( cell );
4006 }
4007
4008 m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
4009
4010 return true;
4011}
4012
4013
4015{
4016 // Drop the table-cell range anchor; the board may have been reloaded and any cached
4017 // pointer is no longer guaranteed to be valid.
4018 m_previousFirstCell = nullptr;
4019
4020 m_selection.Clear();
4021
4022 bool enteredGroupFound = false;
4023
4024 INSPECTOR_FUNC inspector =
4025 [&]( EDA_ITEM* item, void* testData )
4026 {
4027 if( item->IsSelected() )
4028 {
4029 EDA_ITEM* parent = item->GetParent();
4030
4031 // Let selected parents handle their children.
4032 if( parent && parent->IsSelected() )
4034
4035 highlight( item, SELECTED, &m_selection );
4036 }
4037
4038 if( item->Type() == PCB_GROUP_T )
4039 {
4040 if( item == m_enteredGroup )
4041 {
4042 item->SetFlags( ENTERED );
4043 enteredGroupFound = true;
4044 }
4045 else
4046 {
4047 item->ClearFlags( ENTERED );
4048 }
4049 }
4050
4052 };
4053
4056
4057 if( !enteredGroupFound )
4058 {
4059 m_enteredGroupOverlay.Clear();
4060 m_enteredGroup = nullptr;
4061 }
4062}
4063
4064
4065bool PCB_SELECTION_TOOL::Selectable( const BOARD_ITEM* aItem, bool checkVisibilityOnly ) const
4066{
4067 const RENDER_SETTINGS* settings = getView()->GetPainter()->GetSettings();
4068 const PCB_DISPLAY_OPTIONS& options = frame()->GetDisplayOptions();
4069
4070 auto visibleLayers =
4071 [&]() -> LSET
4072 {
4074 {
4075 LSET set;
4076
4077 for( PCB_LAYER_ID layer : LSET::AllLayersMask() )
4078 set.set( layer, view()->IsLayerVisible( layer ) );
4079
4080 return set;
4081 }
4082 else
4083 {
4084 return board()->GetVisibleLayers();
4085 }
4086 };
4087
4088 auto layerVisible =
4089 [&]( PCB_LAYER_ID aLayer )
4090 {
4092 return view()->IsLayerVisible( aLayer );
4093 else
4094 return board()->IsLayerVisible( aLayer );
4095 };
4096
4097 if( settings->GetHighContrast() )
4098 {
4099 const std::set<int> activeLayers = settings->GetHighContrastLayers();
4100 bool onActiveLayer = false;
4101
4102 for( int layer : activeLayers )
4103 {
4104 // NOTE: Only checking the regular layers (not GAL meta-layers)
4105 if( layer < PCB_LAYER_ID_COUNT && aItem->IsOnLayer( ToLAYER_ID( layer ) ) )
4106 {
4107 onActiveLayer = true;
4108 break;
4109 }
4110 }
4111
4112 if( !onActiveLayer && aItem->Type() != PCB_MARKER_T )
4113 {
4114 // We do not want to select items that are in the background
4115 return false;
4116 }
4117 }
4118
4119 if( aItem->Type() == PCB_FOOTPRINT_T )
4120 {
4121 const FOOTPRINT* footprint = static_cast<const FOOTPRINT*>( aItem );
4122
4123 // In footprint editor, we do not want to select the footprint itself.
4125 return false;
4126
4127 // If the footprint has no items except the reference and value fields, include the
4128 // footprint in the selections.
4129 if( footprint->GraphicalItems().empty()
4130 && footprint->Pads().empty()
4131 && footprint->Zones().empty() )
4132 {
4133 return true;
4134 }
4135
4136 for( const BOARD_ITEM* item : footprint->GraphicalItems() )
4137 {
4138 if( Selectable( item, true ) )
4139 return true;
4140 }
4141
4142 for( const PAD* pad : footprint->Pads() )
4143 {
4144 if( Selectable( pad, true ) )
4145 return true;
4146 }
4147
4148 for( const ZONE* zone : footprint->Zones() )
4149 {
4150 if( Selectable( zone, true ) )
4151 return true;
4152 }
4153
4154 for( const PCB_POINT* point: footprint->Points() )
4155 {
4156 if( Selectable( point, true ) )
4157 return true;
4158 }
4159
4160 return false;
4161 }
4162 else if( aItem->Type() == PCB_GROUP_T )
4163 {
4164 PCB_GROUP* group = const_cast<PCB_GROUP*>( static_cast<const PCB_GROUP*>( aItem ) );
4165
4166 // Similar to logic for footprint, a group is selectable if any of its members are.
4167 // (This recurses.)
4168 for( BOARD_ITEM* item : group->GetBoardItems() )
4169 {
4170 if( Selectable( item, true ) )
4171 return true;
4172 }
4173
4174 return false;
4175 }
4176
4177 if( aItem->GetParentGroup() && aItem->GetParentGroup()->AsEdaItem()->Type() == PCB_GENERATOR_T )
4178 {
4179 PCB_GENERATOR* gen = static_cast<PCB_GENERATOR*>( aItem->GetParentGroup()->AsEdaItem() );
4180
4182 return false;
4183 }
4184
4185 const ZONE* zone = nullptr;
4186 const PCB_VIA* via = nullptr;
4187 const PAD* pad = nullptr;
4188 const PCB_TEXT* text = nullptr;
4189 const PCB_FIELD* field = nullptr;
4190 const PCB_MARKER* marker = nullptr;
4191 const PCB_TABLECELL* cell = nullptr;
4192
4193 // Most footprint children can only be selected in the footprint editor.
4194 if( aItem->GetParentFootprint() && !m_isFootprintEditor && !checkVisibilityOnly )
4195 {
4196 if( aItem->Type() != PCB_FIELD_T && aItem->Type() != PCB_PAD_T && aItem->Type() != PCB_TEXT_T )
4197 return false;
4198 }
4199
4200 switch( aItem->Type() )
4201 {
4202 case PCB_ZONE_T:
4203 if( !board()->IsElementVisible( LAYER_ZONES ) || ( options.m_ZoneOpacity == 0.00 ) )
4204 return false;
4205
4206 zone = static_cast<const ZONE*>( aItem );
4207
4208 // A teardrop is modelled as a property of a via, pad or the board (for track-to-track
4209 // teardrops). The underlying zone is only an implementation detail.
4210 if( zone->IsTeardropArea() && !board()->LegacyTeardrops() )
4211 return false;
4212
4213 // zones can exist on multiple layers!
4214 if( !( zone->GetLayerSet() & visibleLayers() ).any() )
4215 return false;
4216
4217 break;
4218
4219 case PCB_TRACE_T:
4220 case PCB_ARC_T:
4221 if( !board()->IsElementVisible( LAYER_TRACKS ) || ( options.m_TrackOpacity == 0.00 ) )
4222 return false;
4223
4224 if( !layerVisible( aItem->GetLayer() ) )
4225 return false;
4226
4227 break;
4228
4229 case PCB_VIA_T:
4230 if( !board()->IsElementVisible( LAYER_VIAS ) || ( options.m_ViaOpacity == 0.00 ) )
4231 return false;
4232
4233 via = static_cast<const PCB_VIA*>( aItem );
4234
4235 // For vias it is enough if only one of its layers is visible
4236 if( !( visibleLayers() & via->GetLayerSet() ).any() )
4237 return false;
4238
4239 break;
4240
4241 case PCB_FIELD_T:
4242 field = static_cast<const PCB_FIELD*>( aItem );
4243
4244 if( !field->IsVisible() )
4245 return false;
4246
4247 if( field->IsReference() && !view()->IsLayerVisible( LAYER_FP_REFERENCES ) )
4248 return false;
4249
4250 if( field->IsValue() && !view()->IsLayerVisible( LAYER_FP_VALUES ) )
4251 return false;
4252
4253 // Handle all other fields with normal text visibility controls
4255 case PCB_TEXT_T:
4256 text = static_cast<const PCB_TEXT*>( aItem );
4257
4258 if( !layerVisible( text->GetLayer() ) )
4259 return false;
4260
4261 // Apply the LOD visibility test as well
4262 if( !view()->IsVisible( text ) )
4263 return false;
4264
4265 if( aItem->GetParentFootprint() )
4266 {
4267 int controlLayer = LAYER_FP_TEXT;
4268
4269 if( text->GetText() == wxT( "${REFERENCE}" ) )
4270 controlLayer = LAYER_FP_REFERENCES;
4271 else if( text->GetText() == wxT( "${VALUE}" ) )
4272 controlLayer = LAYER_FP_VALUES;
4273
4274 if( !view()->IsLayerVisible( controlLayer ) )
4275 return false;
4276 }
4277
4278 break;
4279
4281 if( options.m_ImageOpacity == 0.00 )
4282 return false;
4283
4284 // Bitmap images on board are hidden if LAYER_DRAW_BITMAPS is not visible
4285 if( !view()->IsLayerVisible( LAYER_DRAW_BITMAPS ) )
4286 return false;
4287
4288 if( !layerVisible( aItem->GetLayer() ) )
4289 return false;
4290
4291 break;
4292
4293 case PCB_SHAPE_T:
4294 if( options.m_FilledShapeOpacity == 0.0 && static_cast<const PCB_SHAPE*>( aItem )->IsAnyFill() )
4295 return false;
4296
4297 if( !layerVisible( aItem->GetLayer() ) )
4298 return false;
4299
4300 break;
4301
4302 case PCB_BARCODE_T:
4303 if( !layerVisible( aItem->GetLayer() ) )
4304 return false;
4305
4306 break;
4307
4308 case PCB_TEXTBOX_T:
4309 case PCB_TABLE_T:
4310 case PCB_DRILL_CHART_T:
4311 if( !layerVisible( aItem->GetLayer() ) )
4312 return false;
4313
4314 break;
4315
4316 case PCB_TABLECELL_T:
4317 cell = static_cast<const PCB_TABLECELL*>( aItem );
4318
4319 if( !layerVisible( aItem->GetLayer() ) )
4320 return false;
4321
4322 if( cell->GetRowSpan() == 0 || cell->GetColSpan() == 0 )
4323 return false;
4324
4325 break;
4326
4327 case PCB_DIM_ALIGNED_T:
4328 case PCB_DIM_LEADER_T:
4329 case PCB_DIM_CENTER_T:
4330 case PCB_DIM_RADIAL_T:
4332 if( !layerVisible( aItem->GetLayer() ) )
4333 return false;
4334
4335 break;
4336
4337 case PCB_PAD_T:
4338 if( options.m_PadOpacity == 0.00 )
4339 return false;
4340
4341 pad = static_cast<const PAD*>( aItem );
4342
4343 if( pad->GetAttribute() == PAD_ATTRIB::PTH || pad->GetAttribute() == PAD_ATTRIB::NPTH )
4344 {
4345 // A pad's hole is visible on every layer the pad is visible on plus many layers the
4346 // pad is not visible on -- so we only need to check for any visible hole layers.
4347 if( !( visibleLayers() & LSET::PhysicalLayersMask() ).any() )
4348 return false;
4349 }
4350 else
4351 {
4352 if( !( pad->GetLayerSet() & visibleLayers() ).any() )
4353 return false;
4354 }
4355
4356 break;
4357
4358 case PCB_MARKER_T:
4359 marker = static_cast<const PCB_MARKER*>( aItem );
4360
4361 if( marker && marker->IsExcluded() && !board()->IsElementVisible( LAYER_DRC_EXCLUSION ) )
4362 return false;
4363
4364 break;
4365
4366 case PCB_POINT_T:
4367 if( !layerVisible( aItem->GetLayer() ) )
4368 return false;
4369
4370 if( !board()->IsElementVisible( LAYER_POINTS ) )
4371 return false;
4372
4373 break;
4374
4375 case PCB_GRID_ITEM_T:
4376 if( !board()->IsElementVisible( LAYER_SUBGRIDS ) )
4377 return false;
4378
4379 break;
4380
4381 // These are not selectable
4382 case PCB_NETINFO_T:
4383 case PCB_CONSTRAINT_T: // geometry-free, never rendered or hit-tested (#2329)
4384 case NOT_USED:
4385 case TYPE_NOT_INIT:
4386 return false;
4387
4388 default: // Suppress warnings
4389 break;
4390 }
4391
4392 return true;
4393}
4394
4395
4397{
4398 if( !aItem || aItem->IsSelected() || !aItem->IsBOARD_ITEM() )
4399 return;
4400
4401 if( aItem->Type() == PCB_PAD_T )
4402 {
4403 FOOTPRINT* footprint = static_cast<FOOTPRINT*>( aItem->GetParent() );
4404
4405 if( m_selection.Contains( footprint ) )
4406 return;
4407 }
4408
4409 if( m_enteredGroup && !PCB_GROUP::WithinScope( static_cast<BOARD_ITEM*>( aItem ), m_enteredGroup,
4411 {
4412 ExitGroup();
4413 }
4414
4415 highlight( aItem, SELECTED, &m_selection );
4416}
4417
4418
4420{
4421 unhighlight( aItem, SELECTED, &m_selection );
4422}
4423
4424
4425void PCB_SELECTION_TOOL::highlight( EDA_ITEM* aItem, int aMode, SELECTION* aGroup )
4426{
4427 if( aGroup )
4428 aGroup->Add( aItem );
4429
4430 highlightInternal( aItem, aMode, aGroup != nullptr );
4431 view()->Update( aItem, KIGFX::REPAINT );
4432
4433 // Many selections are very temporal and updating the display each time just
4434 // creates noise.
4435 if( aMode == BRIGHTENED )
4437}
4438
4439
4440void PCB_SELECTION_TOOL::highlightInternal( EDA_ITEM* aItem, int aMode, bool aUsingOverlay )
4441{
4442 if( aMode == SELECTED )
4443 aItem->SetSelected();
4444 else if( aMode == BRIGHTENED )
4445 aItem->SetBrightened();
4446
4447 if( aUsingOverlay && aMode != BRIGHTENED )
4448 view()->Hide( aItem, true ); // Hide the original item, so it is shown only on overlay
4449
4450 if( aItem->IsBOARD_ITEM() )
4451 {
4452 BOARD_ITEM* boardItem = static_cast<BOARD_ITEM*>( aItem );
4453 boardItem->RunOnChildren( std::bind( &PCB_SELECTION_TOOL::highlightInternal, this, _1, aMode, aUsingOverlay ),
4455 }
4456}
4457
4458
4459void PCB_SELECTION_TOOL::unhighlight( EDA_ITEM* aItem, int aMode, SELECTION* aGroup )
4460{
4461 if( aGroup )
4462 aGroup->Remove( aItem );
4463
4464 unhighlightInternal( aItem, aMode, aGroup != nullptr );
4465 view()->Update( aItem, KIGFX::REPAINT );
4466
4467 // Many selections are very temporal and updating the display each time just creates noise.
4468 if( aMode == BRIGHTENED )
4470}
4471
4472
4473void PCB_SELECTION_TOOL::unhighlightInternal( EDA_ITEM* aItem, int aMode, bool aUsingOverlay )
4474{
4475 if( aMode == SELECTED )
4476 aItem->ClearSelected();
4477 else if( aMode == BRIGHTENED )
4478 aItem->ClearBrightened();
4479
4480 if( aUsingOverlay && aMode != BRIGHTENED )
4481 {
4482 view()->Hide( aItem, false ); // Restore original item visibility...
4483 view()->Update( aItem ); // ... and make sure it's redrawn un-selected
4484 }
4485
4486 if( aItem->IsBOARD_ITEM() )
4487 {
4488 BOARD_ITEM* boardItem = static_cast<BOARD_ITEM*>( aItem );
4489 boardItem->RunOnChildren( std::bind( &PCB_SELECTION_TOOL::unhighlightInternal, this, _1, aMode, aUsingOverlay ),
4491 }
4492}
4493
4494
4496{
4497 const unsigned GRIP_MARGIN = 20;
4498 int margin = KiROUND( getView()->ToWorld( GRIP_MARGIN ) );
4499
4500 // Check if the point is located close to any of the currently selected items
4501 for( EDA_ITEM* item : m_selection )
4502 {
4503 if( !item->IsBOARD_ITEM() )
4504 continue;
4505
4506 BOX2I itemBox = item->ViewBBox();
4507 itemBox.Inflate( margin ); // Give some margin for gripping an item
4508
4509 if( itemBox.Contains( aPoint ) )
4510 {
4511 if( item->HitTest( aPoint, margin ) )
4512 return true;
4513
4514 bool found = false;
4515
4516 if( PCB_GROUP* group = dynamic_cast<PCB_GROUP*>( item ) )
4517 {
4518 group->RunOnChildren(
4519 [&]( BOARD_ITEM* aItem )
4520 {
4521 if( aItem->HitTest( aPoint, margin ) )
4522 found = true;
4523 },
4525 }
4526
4527 if( found )
4528 return true;
4529 }
4530 }
4531
4532 return false;
4533}
4534
4535
4536int PCB_SELECTION_TOOL::hitTestDistance( const VECTOR2I& aWhere, BOARD_ITEM* aItem, int aMaxDistance ) const
4537{
4538 BOX2D viewportD = getView()->GetViewport();
4539 BOX2I viewport = BOX2ISafe( viewportD );
4540 int distance = INT_MAX;
4541 SEG loc( aWhere, aWhere );
4542
4543 switch( aItem->Type() )
4544 {
4545 case PCB_FIELD_T:
4546 case PCB_TEXT_T:
4547 {
4548 PCB_TEXT* text = static_cast<PCB_TEXT*>( aItem );
4549
4550 // Add a bit of slop to text-shapes
4551 if( text->GetEffectiveTextShape()->Collide( loc, aMaxDistance, &distance ) )
4552 distance = std::clamp( distance - ( aMaxDistance / 2 ), 0, distance );
4553
4554 break;
4555 }
4556
4557 case PCB_TEXTBOX_T:
4558 {
4559 PCB_TEXTBOX* textbox = static_cast<PCB_TEXTBOX*>( aItem );
4560
4561 // Add a bit of slop to text-shapes
4562 if( textbox->GetEffectiveTextShape()->Collide( loc, aMaxDistance, &distance ) )
4563 distance = std::clamp( distance - ( aMaxDistance / 2 ), 0, distance );
4564
4565 break;
4566 }
4567
4568 case PCB_TABLECELL_T:
4569 {
4570 PCB_TABLECELL* tablecell = static_cast<PCB_TABLECELL*>( aItem );
4571 auto shape = std::make_shared<SHAPE_COMPOUND>( tablecell->MakeEffectiveShapesForHitTesting() );
4572
4573 shape->Collide( loc, aMaxDistance, &distance );
4574
4575 break;
4576 }
4577
4578 case PCB_TABLE_T:
4579 case PCB_DRILL_CHART_T:
4580 {
4581 PCB_TABLE* table = static_cast<PCB_TABLE*>( aItem );
4582 distance = aMaxDistance;
4583
4584 for( PCB_TABLECELL* cell : table->GetCells() )
4585 distance = std::min( distance, hitTestDistance( aWhere, cell, aMaxDistance ) );
4586
4587 // Tables should defer to their table cells. Never consider them exact.
4588 distance = std::clamp( distance + ( aMaxDistance / 4 ), 0, aMaxDistance );
4589 break;
4590 }
4591
4592 case PCB_ZONE_T:
4593 {
4594 ZONE* zone = static_cast<ZONE*>( aItem );
4595
4596 // Zone borders are very specific
4597 if( zone->HitTestForEdge( aWhere, aMaxDistance / 2 ) )
4598 distance = 0;
4599 else if( zone->HitTestForEdge( aWhere, aMaxDistance ) )
4600 distance = aMaxDistance / 2;
4601 else
4602 aItem->GetEffectiveShape()->Collide( loc, aMaxDistance, &distance );
4603
4604 break;
4605 }
4606
4607 case PCB_FOOTPRINT_T:
4608 {
4609 FOOTPRINT* footprint = static_cast<FOOTPRINT*>( aItem );
4610 BOX2I bbox = footprint->GetBoundingBox( false );
4611
4612 try
4613 {
4614 footprint->GetBoundingHull().Collide( loc, aMaxDistance, &distance );
4615 }
4616 catch( const std::exception& e )
4617 {
4618 wxFAIL_MSG( wxString::Format( wxT( "Clipper exception occurred: %s" ), e.what() ) );
4619 }
4620
4621 // Consider footprints larger than the viewport only as a last resort
4622 if( bbox.GetHeight() > viewport.GetHeight() || bbox.GetWidth() > viewport.GetWidth() )
4623 distance = INT_MAX / 2;
4624
4625 break;
4626 }
4627
4628 case PCB_MARKER_T:
4629 {
4630 PCB_MARKER* marker = static_cast<PCB_MARKER*>( aItem );
4631 SHAPE_LINE_CHAIN polygon;
4632
4633 marker->ShapeToPolygon( polygon );
4634 polygon.Move( marker->GetPos() );
4635 polygon.Collide( loc, aMaxDistance, &distance );
4636 break;
4637 }
4638
4639 case PCB_GROUP_T:
4640 case PCB_GENERATOR_T:
4641 {
4642 // Poly-outline generators (e.g. via stitching) define a region; measure distance
4643 // to that outline instead of to their generated children, otherwise clicks landing
4644 // inside the region but away from any child get a huge distance and the generator
4645 // gets pruned by the sloppiness heuristic.
4646 if( const PCB_GENERATOR_POLY* poly = dynamic_cast<const PCB_GENERATOR_POLY*>( aItem ) )
4647 {
4648 int actual = aMaxDistance;
4649
4650 if( poly->Outline().Collide( loc, aMaxDistance, &actual ) )
4651 distance = actual;
4652
4653 break;
4654 }
4655
4656 PCB_GROUP* group = static_cast<PCB_GROUP*>( aItem );
4657
4658 for( BOARD_ITEM* member : group->GetBoardItems() )
4659 distance = std::min( distance, hitTestDistance( aWhere, member, aMaxDistance ) );
4660
4661 break;
4662 }
4663
4664 case PCB_PAD_T:
4665 {
4666 static_cast<PAD*>( aItem )->Padstack().ForEachUniqueLayer(
4667 [&]( PCB_LAYER_ID aLayer )
4668 {
4669 int layerDistance = INT_MAX;
4670 aItem->GetEffectiveShape( aLayer )->Collide( loc, aMaxDistance, &layerDistance );
4671 distance = std::min( distance, layerDistance );
4672 } );
4673
4674 break;
4675 }
4676
4677 default:
4678 aItem->GetEffectiveShape()->Collide( loc, aMaxDistance, &distance );
4679 break;
4680 }
4681
4682 return distance;
4683}
4684
4685
4687{
4688 wxCHECK( m_frame, /* void */ );
4689
4690 if( aCollector.GetCount() < 2 )
4691 return;
4692
4693 const RENDER_SETTINGS* settings = getView()->GetPainter()->GetSettings();
4694
4695 wxCHECK( settings, /* void */ );
4696
4697 PCB_LAYER_ID activeLayer = m_frame->GetActiveLayer();
4698 LSET visibleLayers = m_frame->GetBoard()->GetVisibleLayers();
4699 LSET enabledLayers = m_frame->GetBoard()->GetEnabledLayers();
4700 LSEQ enabledLayerStack = enabledLayers.SeqStackupTop2Bottom( activeLayer );
4701
4702 wxCHECK( !enabledLayerStack.empty(), /* void */ );
4703
4704 auto isZoneFillKeepout =
4705 []( const BOARD_ITEM* aItem ) -> bool
4706 {
4707 if( aItem->Type() == PCB_ZONE_T )
4708 {
4709 const ZONE* zone = static_cast<const ZONE*>( aItem );
4710
4711 if( zone->GetIsRuleArea() && zone->GetDoNotAllowZoneFills() )
4712 return true;
4713 }
4714
4715 return false;
4716 };
4717
4718 std::vector<LAYER_OPACITY_ITEM> opacityStackup;
4719
4720 for( int i = 0; i < aCollector.GetCount(); i++ )
4721 {
4722 const BOARD_ITEM* item = aCollector[i];
4723
4724 LSET itemLayers = item->GetLayerSet() & enabledLayers & visibleLayers;
4725 LSEQ itemLayerSeq = itemLayers.Seq( enabledLayerStack );
4726
4727 for( PCB_LAYER_ID layer : itemLayerSeq )
4728 {
4729 COLOR4D color = settings->GetColor( item, layer );
4730
4731 if( color.a == 0 )
4732 continue;
4733
4734 LAYER_OPACITY_ITEM opacityItem;
4735
4736 opacityItem.m_Layer = layer;
4737 opacityItem.m_Opacity = color.a;
4738 opacityItem.m_Item = item;
4739
4740 if( isZoneFillKeepout( item ) )
4741 opacityItem.m_Opacity = 0.0;
4742
4743 opacityStackup.emplace_back( opacityItem );
4744 }
4745 }
4746
4747 std::sort( opacityStackup.begin(), opacityStackup.end(),
4748 [&]( const LAYER_OPACITY_ITEM& aLhs, const LAYER_OPACITY_ITEM& aRhs ) -> bool
4749 {
4750 int retv = enabledLayerStack.TestLayers( aLhs.m_Layer, aRhs.m_Layer );
4751
4752 if( retv )
4753 return retv > 0;
4754
4755 return aLhs.m_Opacity > aRhs.m_Opacity;
4756 } );
4757
4758 std::set<const BOARD_ITEM*> visibleItems;
4759 std::set<const BOARD_ITEM*> itemsToRemove;
4760 double minAlphaLimit = ADVANCED_CFG::GetCfg().m_PcbSelectionVisibilityRatio;
4761 double currentStackupOpacity = 0.0;
4763
4764 for( const LAYER_OPACITY_ITEM& opacityItem : opacityStackup )
4765 {
4766 if( lastVisibleLayer == PCB_LAYER_ID::UNDEFINED_LAYER )
4767 {
4768 currentStackupOpacity = opacityItem.m_Opacity;
4769 lastVisibleLayer = opacityItem.m_Layer;
4770 visibleItems.emplace( opacityItem.m_Item );
4771 continue;
4772 }
4773
4774 // Objects to ignore and fallback to the old selection behavior.
4775 auto ignoreItem =
4776 [&]()
4777 {
4778 const BOARD_ITEM* item = opacityItem.m_Item;
4779
4780 wxCHECK( item, false );
4781
4782 // Check items that span multiple layers for visibility.
4783 if( visibleItems.count( item ) )
4784 return true;
4785
4786 // Don't prune child items of a footprint that is already visible.
4787 if( item->GetParent()
4788 && ( item->GetParent()->Type() == PCB_FOOTPRINT_T )
4789 && visibleItems.count( item->GetParent() ) )
4790 {
4791 return true;
4792 }
4793
4794 // Keepout zones are transparent but for some reason, PCB_PAINTER::GetColor()
4795 // returns the color of the zone it prevents from filling.
4796 if( isZoneFillKeepout( item ) )
4797 return true;
4798
4799 return false;
4800 };
4801
4802 // Everything on the currently selected layer is visible;
4803 if( opacityItem.m_Layer == enabledLayerStack[0] )
4804 {
4805 visibleItems.emplace( opacityItem.m_Item );
4806 }
4807 else
4808 {
4809 double itemVisibility = opacityItem.m_Opacity * ( 1.0 - currentStackupOpacity );
4810
4811 if( ( itemVisibility <= minAlphaLimit ) && !ignoreItem() )
4812 itemsToRemove.emplace( opacityItem.m_Item );
4813 else
4814 visibleItems.emplace( opacityItem.m_Item );
4815 }
4816
4817 if( opacityItem.m_Layer != lastVisibleLayer )
4818 {
4819 currentStackupOpacity += opacityItem.m_Opacity * ( 1.0 - currentStackupOpacity );
4820 currentStackupOpacity = std::min( currentStackupOpacity, 1.0 );
4821 lastVisibleLayer = opacityItem.m_Layer;
4822 }
4823 }
4824
4825 for( const BOARD_ITEM* itemToRemove : itemsToRemove )
4826 {
4827 wxCHECK( aCollector.GetCount() > 1, /* void */ );
4828 aCollector.Remove( itemToRemove );
4829 }
4830}
4831
4832
4833// The general idea here is that if the user clicks directly on a small item inside a larger
4834// one, then they want the small item. The quintessential case of this is clicking on a pad
4835// within a footprint, but we also apply it for text within a footprint, footprints within
4836// larger footprints, and vias within either larger pads or longer tracks.
4837//
4838// These "guesses" presume there is area within the larger item to click in to select it. If
4839// an item is mostly covered by smaller items within it, then the guesses are inappropriate as
4840// there might not be any area left to click to select the larger item. In this case we must
4841// leave the items in the collector and bring up a Selection Clarification menu.
4842//
4843// We currently check for pads and text mostly covering a footprint, but we don't check for
4844// smaller footprints mostly covering a larger footprint.
4845//
4847 const VECTOR2I& aWhere ) const
4848{
4849 static const LSET silkLayers( { B_SilkS, F_SilkS } );
4850 static const LSET courtyardLayers( { B_CrtYd, F_CrtYd } );
4851 static std::vector<KICAD_T> singleLayerSilkTypes = { PCB_FIELD_T,
4855 PCB_BARCODE_T };
4856
4857 if( ADVANCED_CFG::GetCfg().m_PcbSelectionVisibilityRatio != 1.0 )
4859
4860 if( aCollector.GetCount() == 1 )
4861 return;
4862
4863 std::set<BOARD_ITEM*> preferred;
4864 std::set<BOARD_ITEM*> rejected;
4865 VECTOR2I where( aWhere.x, aWhere.y );
4866 const RENDER_SETTINGS* settings = getView()->GetPainter()->GetSettings();
4867 PCB_LAYER_ID activeLayer = m_frame->GetActiveLayer();
4868
4869 // A map's marks sit on the holes they mark, so a plain click falls through to the via or
4870 // pad under them. Holding the click skips these heuristics and reaches the map
4871 for( int i = aCollector.GetCount() - 1; i >= 0; --i )
4872 {
4873 if( aCollector[i]->Type() != PCB_DRILL_MAP_T )
4874 continue;
4875
4876 if( aCollector[i]->GetLayer() == activeLayer )
4877 preferred.insert( aCollector[i] );
4878 else if( aCollector.GetCount() > 1 )
4879 aCollector.Remove( i );
4880 }
4881
4882 // A drill chart says what the board says, so a click on it is a click on the chart. Its
4883 // cells stay collectable for everything else, and a double-click still reaches them.
4884 for( int i = aCollector.GetCount() - 1; i >= 0; --i )
4885 {
4886 const BOARD_ITEM* parent = aCollector[i]->GetParent();
4887
4888 if( aCollector[i]->Type() == PCB_TABLECELL_T && parent
4889 && parent->Type() == PCB_DRILL_CHART_T && aCollector.HasItem( parent ) )
4890 {
4891 aCollector.Remove( i );
4892 }
4893 }
4894
4895 // If a silk layer is in front, we assume the user is working with silk and give preferential
4896 // treatment to single-layer items on *either* silk layer.
4897 if( silkLayers[activeLayer] )
4898 {
4899 for( int i = 0; i < aCollector.GetCount(); ++i )
4900 {
4901 BOARD_ITEM* item = aCollector[i];
4902
4903 if( item->IsType( singleLayerSilkTypes ) && silkLayers[ item->GetLayer() ] )
4904 preferred.insert( item );
4905 }
4906 }
4907 // Similarly, if a courtyard layer is in front, we assume the user is positioning footprints
4908 // and give preferential treatment to footprints on *both* top and bottom.
4909 else if( courtyardLayers[activeLayer] && settings->GetHighContrast() )
4910 {
4911 for( int i = 0; i < aCollector.GetCount(); ++i )
4912 {
4913 BOARD_ITEM* item = aCollector[i];
4914
4915 if( item->Type() == PCB_FOOTPRINT_T )
4916 preferred.insert( item );
4917 }
4918 }
4919
4920 if( preferred.size() > 0 )
4921 {
4922 aCollector.Empty();
4923
4924 for( BOARD_ITEM* item : preferred )
4925 aCollector.Append( item );
4926
4927 if( preferred.size() == 1 )
4928 return;
4929 }
4930
4931 // Prefer exact hits to sloppy ones
4932 constexpr int MAX_SLOP = 5;
4933
4934 int singlePixel = KiROUND( aCollector.GetGuide()->OnePixelInIU() );
4935 int maxSlop = KiROUND( MAX_SLOP * aCollector.GetGuide()->OnePixelInIU() );
4936 int minSlop = INT_MAX;
4937
4938 std::map<BOARD_ITEM*, int> itemsBySloppiness;
4939
4940 for( int i = 0; i < aCollector.GetCount(); ++i )
4941 {
4942 BOARD_ITEM* item = aCollector[i];
4943 int itemSlop = hitTestDistance( where, item, maxSlop );
4944
4945 itemsBySloppiness[ item ] = itemSlop;
4946
4947 if( itemSlop < minSlop )
4948 minSlop = itemSlop;
4949 }
4950
4951 // Prune sloppier items
4952 if( minSlop < INT_MAX )
4953 {
4954 for( std::pair<BOARD_ITEM*, int> pair : itemsBySloppiness )
4955 {
4956 if( pair.second > minSlop + singlePixel )
4957 aCollector.Transfer( pair.first );
4958 }
4959 }
4960
4961 // If the user clicked on a small item within a much larger one then it's pretty clear
4962 // they're trying to select the smaller one.
4963 constexpr double sizeRatio = 1.5;
4964
4965 std::vector<std::pair<BOARD_ITEM*, double>> itemsByArea;
4966
4967 for( int i = 0; i < aCollector.GetCount(); ++i )
4968 {
4969 BOARD_ITEM* item = aCollector[i];
4970 double area = 0.0;
4971
4972 if( item->Type() == PCB_ZONE_T
4973 && static_cast<ZONE*>( item )->HitTestForEdge( where, maxSlop / 2 ) )
4974 {
4975 // Zone borders are very specific, so make them "small"
4976 area = (double) SEG::Square( singlePixel ) * MAX_SLOP;
4977 }
4978 else if( item->Type() == PCB_VIA_T )
4979 {
4980 // Vias rarely hide other things, and we don't want them deferring to short track
4981 // segments underneath them -- so artificially reduce their size from πr² to r².
4982 area = (double) SEG::Square( static_cast<PCB_VIA*>( item )->GetDrill() / 2 );
4983 }
4984 else if( item->Type() == PCB_REFERENCE_IMAGE_T )
4985 {
4986 BOX2I box = item->GetBoundingBox();
4987 area = (double) box.GetWidth() * box.GetHeight();
4988 }
4989 else
4990 {
4991 try
4992 {
4993 area = FOOTPRINT::GetCoverageArea( item, aCollector );
4994 }
4995 catch( const std::exception& e )
4996 {
4997 wxFAIL_MSG( wxString::Format( wxT( "Clipper exception occurred: %s" ), e.what() ) );
4998 }
4999 }
5000
5001 itemsByArea.emplace_back( item, area );
5002 }
5003
5004 std::sort( itemsByArea.begin(), itemsByArea.end(),
5005 []( const std::pair<BOARD_ITEM*, double>& lhs,
5006 const std::pair<BOARD_ITEM*, double>& rhs ) -> bool
5007 {
5008 return lhs.second < rhs.second;
5009 } );
5010
5011 bool rejecting = false;
5012
5013 for( int i = 1; i < (int) itemsByArea.size(); ++i )
5014 {
5015 if( itemsByArea[i].second > itemsByArea[i-1].second * sizeRatio )
5016 rejecting = true;
5017
5018 if( rejecting )
5019 rejected.insert( itemsByArea[i].first );
5020 }
5021
5022 // Special case: if a footprint is completely covered with other features then there's no
5023 // way to select it -- so we need to leave it in the list for user disambiguation.
5024 constexpr double maxCoverRatio = 0.70;
5025
5026 for( int i = 0; i < aCollector.GetCount(); ++i )
5027 {
5028 if( FOOTPRINT* footprint = dynamic_cast<FOOTPRINT*>( aCollector[i] ) )
5029 {
5030 if( footprint->CoverageRatio( aCollector ) > maxCoverRatio )
5031 rejected.erase( footprint );
5032 }
5033 }
5034
5035 // Hopefully we've now got what the user wanted.
5036 if( (unsigned) aCollector.GetCount() > rejected.size() ) // do not remove everything
5037 {
5038 for( BOARD_ITEM* item : rejected )
5039 aCollector.Transfer( item );
5040 }
5041
5042 // Finally, what we are left with is a set of items of similar coverage area. We now reject
5043 // any that are not on the active layer, to reduce the number of disambiguation menus shown.
5044 // If the user wants to force-disambiguate, they can either switch layers or use the modifier
5045 // key to force the menu.
5046 if( aCollector.GetCount() > 1 )
5047 {
5048 bool haveItemOnActive = false;
5049 rejected.clear();
5050
5051 for( int i = 0; i < aCollector.GetCount(); ++i )
5052 {
5053 if( !aCollector[i]->IsOnLayer( activeLayer ) )
5054 rejected.insert( aCollector[i] );
5055 else
5056 haveItemOnActive = true;
5057 }
5058
5059 if( haveItemOnActive )
5060 {
5061 for( BOARD_ITEM* item : rejected )
5062 aCollector.Transfer( item );
5063 }
5064 }
5065}
5066
5067
5069{
5071 {
5072 m_frame->ShowInfoBarWarning( _( "Selection contains locked items. "
5073 "Enable 'Override locks' to operate on them." ),
5074 true );
5075 }
5076
5077 return m_lockedItemsFiltered;
5078}
5079
5080
5082{
5083 bool lockedDescendant = false;
5084
5085 aItem->RunOnChildren(
5086 [&]( BOARD_ITEM* curr_item )
5087 {
5088 if( !curr_item->GetParentFootprint() && curr_item->IsLocked() )
5089 lockedDescendant = true;
5090 },
5092
5093 return lockedDescendant;
5094}
5095
5096
5097bool PCB_SELECTION_TOOL::isWithinEnteredGroup( BOARD_ITEM* aItem, PCB_GROUP* aEnteredGroup, bool aIsFootprintEditor )
5098{
5099 if( aEnteredGroup )
5100 return PCB_GROUP::WithinScope( aItem, aEnteredGroup, aIsFootprintEditor );
5101
5102 // Not entered: keep expansion at the top level so it can't reach into a group and
5103 // silently pull the whole group into a later delete.
5104 return aItem->GetParentGroup() == nullptr;
5105}
5106
5107
5109{
5110 m_lockedItemsFiltered = false;
5111
5112 if( m_frame && m_frame->IsType( FRAME_PCB_EDITOR ) && !m_frame->GetOverrideLocks() )
5113 {
5114 // Iterate from the back so we don't have to worry about removals.
5115 for( int i = (int) aCollector.GetCount() - 1; i >= 0; --i )
5116 {
5117 BOARD_ITEM* item = aCollector[i];
5118
5119 if( item->IsLocked() || HasLockedDescendant( item ) )
5120 {
5121 aCollector.Remove( item );
5122 m_lockedItemsFiltered = true;
5123 }
5124 }
5125 }
5126}
5127
5128
5129void PCB_SELECTION_TOOL::FilterCollectorForHierarchy( GENERAL_COLLECTOR& aCollector, bool aMultiselect ) const
5130{
5131 std::unordered_set<EDA_ITEM*> toAdd;
5132
5133 // Set CANDIDATE on all parents which are included in the GENERAL_COLLECTOR. This
5134 // algorithm is O(3n), whereas checking for the parent inclusion could potentially be O(n^2).
5135 for( int j = 0; j < aCollector.GetCount(); j++ )
5136 {
5137 if( aCollector[j]->GetParent() )
5138 aCollector[j]->GetParent()->ClearFlags( CANDIDATE );
5139
5140 if( aCollector[j]->GetParentFootprint() )
5141 aCollector[j]->GetParentFootprint()->ClearFlags( CANDIDATE );
5142 }
5143
5144 if( aMultiselect )
5145 {
5146 for( int j = 0; j < aCollector.GetCount(); j++ )
5147 aCollector[j]->SetFlags( CANDIDATE );
5148 }
5149
5150 for( int j = 0; j < aCollector.GetCount(); )
5151 {
5152 BOARD_ITEM* item = aCollector[j];
5153 FOOTPRINT* fp = item->GetParentFootprint();
5154 BOARD_ITEM* start = item;
5155
5156 if( !m_isFootprintEditor && fp )
5157 start = fp;
5158
5159 // If a group is entered, disallow selections of objects outside the group.
5161 {
5162 aCollector.Remove( item );
5163 continue;
5164 }
5165
5166 // If any element is a member of a group, replace those elements with the top containing
5167 // group. Exception: generators that mark their children as individually selectable
5168 // (e.g. via stitching) — keep the child and drop the parent generator from the
5169 // collector so the child wins over the surrounding generator shape.
5170 PCB_GENERATOR* selectableParent = nullptr;
5171
5172 if( EDA_GROUP* parent = item->GetParentGroup() )
5173 {
5174 if( parent->AsEdaItem()->Type() == PCB_GENERATOR_T )
5175 {
5176 PCB_GENERATOR* gen = static_cast<PCB_GENERATOR*>( parent->AsEdaItem() );
5177
5179 selectableParent = gen;
5180 }
5181 }
5182
5183 if( !selectableParent )
5184 {
5186 {
5187 if( top->AsEdaItem() != item )
5188 {
5189 toAdd.insert( top->AsEdaItem() );
5190 top->AsEdaItem()->SetFlags( CANDIDATE );
5191
5192 aCollector.Remove( item );
5193 continue;
5194 }
5195 }
5196 }
5197 else
5198 {
5199 for( int k = aCollector.GetCount() - 1; k >= 0; --k )
5200 {
5201 if( aCollector[k] == selectableParent )
5202 {
5203 aCollector.Remove( k );
5204
5205 if( k < j )
5206 --j;
5207
5208 break;
5209 }
5210 }
5211 }
5212
5213 // Footprints are a bit easier as they can't be nested.
5214 if( fp && ( fp->GetFlags() & CANDIDATE ) )
5215 {
5216 // Remove children of selected items
5217 aCollector.Remove( item );
5218 continue;
5219 }
5220
5221 ++j;
5222 }
5223
5224 for( EDA_ITEM* item : toAdd )
5225 {
5226 if( !aCollector.HasItem( item ) )
5227 aCollector.Append( item );
5228 }
5229}
5230
5231
5233{
5234 std::set<BOARD_ITEM*> to_add;
5235
5236 // Iterate from the back so we don't have to worry about removals.
5237 for( int i = (int) aCollector.GetCount() - 1; i >= 0; --i )
5238 {
5239 BOARD_ITEM* item = aCollector[i];
5240
5241 if( item->Type() == PCB_TABLECELL_T )
5242 {
5243 if( !aCollector.HasItem( item->GetParent() ) )
5244 to_add.insert( item->GetParent() );
5245
5246 aCollector.Remove( item );
5247 }
5248 }
5249
5250 for( BOARD_ITEM* item : to_add )
5251 aCollector.Append( item );
5252}
5253
5254
5256 bool aForcePromotion ) const
5257{
5258 std::set<BOARD_ITEM*> to_add;
5259
5260 // Iterate from the back so we don't have to worry about removals.
5261 for( int i = aCollector.GetCount() - 1; i >= 0; --i )
5262 {
5263 BOARD_ITEM* item = aCollector[i];
5264
5265 if( !m_isFootprintEditor && item->Type() == PCB_PAD_T
5266 && ( !frame()->GetPcbNewSettings()->m_AllowFreePads || aForcePromotion ) )
5267 {
5268 if( !aCollector.HasItem( item->GetParent() ) )
5269 to_add.insert( item->GetParent() );
5270
5271 aCollector.Remove( item );
5272 }
5273 }
5274
5275 for( BOARD_ITEM* item : to_add )
5276 aCollector.Append( item );
5277}
5278
5279
5281{
5282 // Iterate from the back so we don't have to worry about removals.
5283 for( int i = aCollector.GetCount() - 1; i >= 0; --i )
5284 {
5285 BOARD_ITEM* item = aCollector[i];
5286
5287 if( item->Type() == PCB_MARKER_T )
5288 aCollector.Remove( item );
5289 }
5290}
5291
5292
5294 const VECTOR2I& aWhere ) const
5295{
5296 const RENDER_SETTINGS* settings = getView()->GetPainter()->GetSettings();
5297 BOX2D viewport = getView()->GetViewport();
5298 BOX2I extents = BOX2ISafe( viewport );
5299
5300 bool need_direct_hit = false;
5301 FOOTPRINT* single_fp = nullptr;
5302
5303 // If the designer is not modifying the existing selection AND we already have
5304 // a selection, then we only want to select items that are directly under the cursor.
5305 // This prevents us from being unable to clear the selection when zoomed into a footprint
5306 if( !m_additive && !m_subtractive && !m_exclusive_or && m_selection.GetSize() > 0 )
5307 {
5308 need_direct_hit = true;
5309
5310 for( EDA_ITEM* item : m_selection )
5311 {
5312 FOOTPRINT* fp = nullptr;
5313
5314 if( item->Type() == PCB_FOOTPRINT_T )
5315 fp = static_cast<FOOTPRINT*>( item );
5316 else if( item->IsBOARD_ITEM() )
5317 fp = static_cast<BOARD_ITEM*>( item )->GetParentFootprint();
5318
5319 // If the selection contains items that are not footprints, then don't restrict
5320 // whether we deselect the item or not.
5321 if( !fp )
5322 {
5323 single_fp = nullptr;
5324 break;
5325 }
5326 else if( !single_fp )
5327 {
5328 single_fp = fp;
5329 }
5330 // If the selection contains items from multiple footprints, then don't restrict
5331 // whether we deselect the item or not.
5332 else if( single_fp != fp )
5333 {
5334 single_fp = nullptr;
5335 break;
5336 }
5337 }
5338 }
5339
5340 auto visibleLayers =
5341 [&]() -> LSET
5342 {
5344 {
5345 LSET set;
5346
5347 for( PCB_LAYER_ID layer : LSET::AllLayersMask() )
5348 set.set( layer, view()->IsLayerVisible( layer ) );
5349
5350 return set;
5351 }
5352 else
5353 {
5354 return board()->GetVisibleLayers();
5355 }
5356 };
5357
5358 LSET layers = visibleLayers();
5359
5360 if( settings->GetHighContrast() )
5361 {
5362 layers.reset();
5363
5364 const std::set<int> activeLayers = settings->GetHighContrastLayers();
5365
5366 for( int layer : activeLayers )
5367 {
5368 if( layer >= 0 && layer < PCB_LAYER_ID_COUNT )
5369 layers.set( layer );
5370 }
5371 }
5372
5373 // Iterate from the back so we don't have to worry about removals.
5374 for( int i = aCollector.GetCount() - 1; i >= 0; --i )
5375 {
5376 BOARD_ITEM* item = aCollector[i];
5377 FOOTPRINT* fp = dyn_cast<FOOTPRINT*>( item );
5378
5379 if( !fp )
5380 continue;
5381
5382 // Make footprints not difficult to select in high-contrast modes.
5383 if( layers[fp->GetLayer()] )
5384 continue;
5385
5386 BOX2I bbox = fp->GetLayerBoundingBox( layers );
5387
5388 // If the point clicked is not inside the visible bounding box, we can also remove it.
5389 if( !bbox.Contains( aWhere) )
5390 aCollector.Remove( item );
5391
5392 bool has_hit = false;
5393
5394 for( PCB_LAYER_ID layer : layers )
5395 {
5396 if( fp->HitTestOnLayer( extents, false, layer ) )
5397 {
5398 has_hit = true;
5399 break;
5400 }
5401 }
5402
5403 // If the point is outside of the visible bounding box, we can remove it.
5404 if( !has_hit )
5405 {
5406 aCollector.Remove( item );
5407 }
5408 // Do not require a direct hit on this fp if the existing selection only contains
5409 // this fp's items. This allows you to have a selection of pads from a single
5410 // footprint and still click in the center of the footprint to select it.
5411 else if( single_fp )
5412 {
5413 if( fp == single_fp )
5414 continue;
5415 }
5416 else if( need_direct_hit )
5417 {
5418 has_hit = false;
5419
5420 for( PCB_LAYER_ID layer : layers )
5421 {
5422 if( fp->HitTestOnLayer( aWhere, layer ) )
5423 {
5424 has_hit = true;
5425 break;
5426 }
5427 }
5428
5429 if( !has_hit )
5430 aCollector.Remove( item );
5431 }
5432 }
5433}
5434
5435
5437{
5438 getView()->Update( &m_selection );
5440
5441 return 0;
5442}
5443
5444
5446{
5447 std::set<std::pair<PCB_TABLE*, int>> columns;
5448 bool added = false;
5449
5450 for( EDA_ITEM* item : m_selection )
5451 {
5452 if( PCB_TABLECELL* cell = dynamic_cast<PCB_TABLECELL*>( item ) )
5453 {
5454 PCB_TABLE* table = static_cast<PCB_TABLE*>( cell->GetParent() );
5455 columns.insert( std::make_pair( table, cell->GetColumn() ) );
5456 }
5457 }
5458
5459 for( auto& [ table, col ] : columns )
5460 {
5461 for( int row = 0; row < table->GetRowCount(); ++row )
5462 {
5463 PCB_TABLECELL* cell = table->GetCell( row, col );
5464
5465 if( !cell->IsSelected() )
5466 {
5467 select( table->GetCell( row, col ) );
5468 added = true;
5469 }
5470 }
5471 }
5472
5473 if( added )
5474 m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
5475
5476 return 0;
5477}
5478
5479
5481{
5482 std::set<std::pair<PCB_TABLE*, int>> rows;
5483 bool added = false;
5484
5485 for( EDA_ITEM* item : m_selection )
5486 {
5487 if( PCB_TABLECELL* cell = dynamic_cast<PCB_TABLECELL*>( item ) )
5488 {
5489 PCB_TABLE* table = static_cast<PCB_TABLE*>( cell->GetParent() );
5490 rows.insert( std::make_pair( table, cell->GetRow() ) );
5491 }
5492 }
5493
5494 for( auto& [ table, row ] : rows )
5495 {
5496 for( int col = 0; col < table->GetColCount(); ++col )
5497 {
5498 PCB_TABLECELL* cell = table->GetCell( row, col );
5499
5500 if( !cell->IsSelected() )
5501 {
5502 select( table->GetCell( row, col ) );
5503 added = true;
5504 }
5505 }
5506 }
5507
5508 if( added )
5509 m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
5510
5511 return 0;
5512}
5513
5514
5516{
5517 std::set<PCB_TABLE*> tables;
5518 bool added = false;
5519
5520 for( EDA_ITEM* item : m_selection )
5521 {
5522 if( PCB_TABLECELL* cell = dynamic_cast<PCB_TABLECELL*>( item ) )
5523 tables.insert( static_cast<PCB_TABLE*>( cell->GetParent() ) );
5524 }
5525
5527
5528 for( PCB_TABLE* table : tables )
5529 {
5530 if( !table->IsSelected() )
5531 {
5532 select( table );
5533 added = true;
5534 }
5535 }
5536
5537 if( added )
5538 m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
5539
5540 return 0;
5541}
5542
5543
5545{
5547
5551
5558
5577
5582
5584}
std::function< void(const VECTOR2I &, GENERAL_COLLECTOR &, PCB_SELECTION_TOOL *)> CLIENT_SELECTION_FILTER
Definition actions.h:33
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:121
constexpr BOX2I BOX2ISafe(const BOX2D &aInput)
Definition box2.h:934
BOX2< VECTOR2I > BOX2I
Definition box2.h:927
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:995
BOX2< VECTOR2D > BOX2D
Definition box2.h:928
static TOOL_ACTION cancelInteractive
Definition actions.h:68
static TOOL_ACTION unselectAll
Definition actions.h:79
static TOOL_ACTION selectItem
Select an item (specified as the event parameter).
Definition actions.h:223
static TOOL_ACTION cursorLeft
Definition actions.h:168
static TOOL_ACTION zoomOutCenter
Definition actions.h:132
static TOOL_ACTION unselectItem
Definition actions.h:224
static TOOL_ACTION zoomIn
Definition actions.h:129
static TOOL_ACTION cursorLeftFast
Definition actions.h:173
static TOOL_ACTION selectionCursor
Select a single item under the cursor position.
Definition actions.h:213
static TOOL_ACTION selectSetLasso
Definition actions.h:217
static TOOL_ACTION selectSetRect
Set lasso selection mode.
Definition actions.h:216
static TOOL_ACTION groupEnter
Definition actions.h:239
static TOOL_ACTION selectColumns
Definition actions.h:98
static TOOL_ACTION cursorDown
Definition actions.h:167
static TOOL_ACTION zoomOut
Definition actions.h:130
static TOOL_ACTION cursorRightFast
Definition actions.h:174
static TOOL_ACTION deleteLastPoint
Definition actions.h:269
static TOOL_ACTION zoomCenter
Definition actions.h:137
static TOOL_ACTION panDown
Definition actions.h:181
static TOOL_ACTION cursorDblClick
Definition actions.h:177
static TOOL_ACTION undo
Definition actions.h:71
static TOOL_ACTION selectionActivate
Activation of the selection tool.
Definition actions.h:210
static TOOL_ACTION cursorDownFast
Definition actions.h:172
static TOOL_ACTION selectionMenu
Run a selection menu to select from a list of items.
Definition actions.h:232
static TOOL_ACTION reselectItem
Definition actions.h:225
static TOOL_ACTION selectRows
Definition actions.h:97
static TOOL_ACTION cursorUpFast
Definition actions.h:171
static TOOL_ACTION panLeft
Definition actions.h:182
static TOOL_ACTION updateMenu
Definition actions.h:266
static TOOL_ACTION doDelete
Definition actions.h:81
static TOOL_ACTION selectionTool
Definition actions.h:247
static TOOL_ACTION cursorClick
Definition actions.h:176
static TOOL_ACTION zoomFitScreen
Definition actions.h:138
static TOOL_ACTION increment
Definition actions.h:90
static TOOL_ACTION selectionClear
Clear the current selection.
Definition actions.h:220
static TOOL_ACTION panUp
Definition actions.h:180
static TOOL_ACTION zoomFitObjects
Definition actions.h:139
static TOOL_ACTION zoomInCenter
Definition actions.h:131
static TOOL_ACTION panRight
Definition actions.h:183
static TOOL_ACTION selectTable
Definition actions.h:99
static TOOL_ACTION cursorUp
Cursor control with keyboard.
Definition actions.h:166
static TOOL_ACTION groupLeave
Definition actions.h:240
static TOOL_ACTION finishInteractive
Definition actions.h:69
static TOOL_ACTION cursorRight
Definition actions.h:169
static TOOL_ACTION selectAll
Definition actions.h:78
static TOOL_ACTION unselectItems
Definition actions.h:229
static TOOL_ACTION selectItems
Select a list of items (specified as the event parameter)
Definition actions.h:228
Define the structure of a menu based on ACTIONs.
Definition action_menu.h:43
ACTION_MENU(bool isContextMenu, TOOL_INTERACTIVE *aTool=nullptr)
Default constructor.
TOOL_MANAGER * getToolManager() const
Return an instance of TOOL_MANAGER class.
void Clear()
Remove all the entries from the menu (as well as its title).
void SetTitle(const wxString &aTitle) override
Set title for the menu.
wxMenuItem * Add(const wxString &aLabel, int aId, BITMAPS aIcon)
Add a wxWidgets-style entry to the menu.
static const ADVANCED_CFG & GetCfg()
Get the singleton instance's config, which is shared by all consumers.
BASE_SET & reset(size_t pos)
Definition base_set.h:153
BASE_SET & set(size_t pos)
Definition base_set.h:126
virtual void Push(const wxString &aMessage=wxEmptyString, int aCommitFlags=0) override
Execute the changes.
A base class derived from BOARD_ITEM for items that can be connected and have a net,...
static bool ClassOf(const EDA_ITEM *aItem)
Returns information if the object is derived from BOARD_CONNECTED_ITEM.
NETINFO_ITEM * GetNet() const
Return #NET_INFO object for a given item.
Tool for pcb inspection.
int ClearHighlight(const TOOL_EVENT &aEvent)
Perform the appropriate action in response to an Eeschema cross-probe.
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:84
virtual bool IsConnected() const
Returns information if the object is derived from BOARD_CONNECTED_ITEM.
Definition board_item.h:172
bool IsLocked() const override
virtual PCB_LAYER_ID GetLayer() const
Return the primary layer this item is on.
virtual VECTOR2I GetCenter() const
This defaults to the center of the bounding box if not overridden.
Definition board_item.h:150
FOOTPRINT * GetParentFootprint() const
virtual LSET GetLayerSet() const
Return a std::bitset of all layers on which the item physically resides.
Definition board_item.h:346
virtual void RunOnChildren(const std::function< void(BOARD_ITEM *)> &aFunction, RECURSE_MODE aMode) const
Invoke a function on all children.
Definition board_item.h:264
BOARD_ITEM_CONTAINER * GetParent() const
Definition board_item.h:266
virtual bool IsOnCopperLayer() const
Definition board_item.h:189
virtual std::shared_ptr< SHAPE > GetEffectiveShape(PCB_LAYER_ID aLayer=UNDEFINED_LAYER, FLASHING aFlash=FLASHING::DEFAULT, DRC_CONSTRAINT_T aUsage=NULL_CONSTRAINT) const
Some pad shapes can be complex (rounded/chamfered rectangle), even without considering custom shapes.
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:409
INSPECT_RESULT Visit(INSPECTOR inspector, void *testData, const std::vector< KICAD_T > &scanTypes) override
May be re-implemented for each derived class in order to handle all the types given by its member dat...
Definition board.cpp:2828
bool IsElementVisible(GAL_LAYER_ID aLayer) const
Test whether a given element category is visible.
Definition board.cpp:1250
const LSET & GetVisibleLayers() const
A proxy function that calls the correspondent function in m_BoardSettings.
Definition board.cpp:1197
FOOTPRINT * GetFirstFootprint() const
Get the first footprint on the board or nullptr.
Definition board.h:704
bool IsLayerVisible(PCB_LAYER_ID aLayer) const
A proxy function that calls the correspondent function in m_BoardSettings tests whether a given layer...
Definition board.cpp:1189
std::shared_ptr< CONNECTIVITY_DATA > GetConnectivity() const
Return a list of missing connections between components/tracks.
Definition board.h:751
constexpr void SetMaximum()
Definition box2.h:77
constexpr BOX2< Vec > & Inflate(coord_type dx, coord_type dy)
Inflates the rectangle horizontally by dx and vertically by dy.
Definition box2.h:553
constexpr BOX2< Vec > & Normalize()
Ensure that the height and width are positive.
Definition box2.h:143
constexpr size_type GetWidth() const
Definition box2.h:211
constexpr Vec Centre() const
Definition box2.h:94
constexpr size_type GetHeight() const
Definition box2.h:212
constexpr bool Contains(const Vec &aPoint) const
Definition box2.h:165
constexpr const Vec & GetOrigin() const
Definition box2.h:207
constexpr const SizeVec & GetSize() const
Definition box2.h:203
CN_ANCHOR represents a physical location that can be connected: a pad or a track/arc/via endpoint.
BOARD_CONNECTED_ITEM * Parent() const
CN_EDGE represents a point-to-point connection, whether realized or unrealized (ie: tracks etc.
CN_ITEM represents a BOARD_CONNETED_ITEM in the connectivity system (ie: a pad, track/arc/via,...
virtual double OnePixelInIU() const =0
void Transfer(int aIndex)
Move the item at aIndex (first position is 0) to the backup list.
Definition collector.h:149
void Empty()
Clear the list.
Definition collector.h:87
ITER begin()
Definition collector.h:71
int GetCount() const
Return the number of objects in the list.
Definition collector.h:79
bool HasItem(const EDA_ITEM *aItem) const
Tests if aItem has already been collected.
Definition collector.h:193
void Remove(int aIndex)
Remove the item at aIndex (first position is 0).
Definition collector.h:107
ITER end()
Definition collector.h:72
void Append(EDA_ITEM *item)
Add an item to the end of the list.
Definition collector.h:97
COMMIT & Remove(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr)
Remove a new item from the model.
Definition commit.h:86
Interactive authoring of geometric constraints (issue #2329).
bool ClearConstraintSelection()
Clear any badge-selected constraint. Returns true if a constraint was selected and is now cleared.
bool EditConstraintAt(const VECTOR2I &aPos)
Open the value dialog for the constraint badged at aPos; returns true if one was hit.
int ShowModal() override
A set of EDA_ITEMs (i.e., without duplicates).
Definition eda_group.h:43
std::unordered_set< EDA_ITEM * > & GetItems()
Definition eda_group.h:64
bool HasDesignBlockLink() const
Definition eda_group.h:85
virtual EDA_ITEM * AsEdaItem()=0
A base class for most all the KiCad significant classes used in schematics and boards.
Definition eda_item.h:98
virtual VECTOR2I GetPosition() const
Definition eda_item.h:348
virtual const BOX2I GetBoundingBox() const
Return the orthogonal bounding box of this object for display purposes.
Definition eda_item.cpp:270
void SetFlags(EDA_ITEM_FLAGS aMask)
Definition eda_item.h:158
const KIID m_Uuid
Definition eda_item.h:597
virtual EDA_GROUP * GetParentGroup() const
Definition eda_item.h:116
KICAD_T Type() const
Returns the type of object.
Definition eda_item.h:110
void ClearSelected()
Definition eda_item.h:153
void ClearFlags(EDA_ITEM_FLAGS aMask=EDA_ITEM_ALL_FLAGS)
Definition eda_item.h:160
bool IsSelected() const
Definition eda_item.h:134
void SetSelected()
Definition eda_item.h:150
virtual bool IsType(const std::vector< KICAD_T > &aScanTypes) const
Check whether the item is one of the listed types.
Definition eda_item.h:214
void ClearBrightened()
Definition eda_item.h:154
void SetBrightened()
Definition eda_item.h:151
virtual bool HitTest(const VECTOR2I &aPosition, int aAccuracy=0) const
Test if aPosition is inside or on the boundary of this item.
Definition eda_item.h:309
EDA_ITEM * GetParent() const
Definition eda_item.h:112
bool HasFlag(EDA_ITEM_FLAGS aFlag) const
Definition eda_item.h:168
EDA_ITEM_FLAGS GetFlags() const
Definition eda_item.h:167
SHAPE_T GetShape() const
Definition eda_shape.h:175
const VECTOR2I & GetEnd() const
Return the ending point of the graphic.
Definition eda_shape.h:325
bool IsClosed() const
const VECTOR2I & GetStart() const
Return the starting point of the graphic.
Definition eda_shape.h:275
bool IsAnyFill() const
Definition eda_shape.h:118
virtual std::vector< SHAPE * > MakeEffectiveShapesForHitTesting() const
Definition eda_shape.h:569
virtual bool IsVisible() const
Definition eda_text.h:226
std::shared_ptr< SHAPE_COMPOUND > GetEffectiveTextShape(bool aTriangulate=true, const BOX2I &aBBox=BOX2I(), const EDA_ANGLE &aAngle=ANGLE_0) const
build a list of segments (SHAPE_SEGMENT) to describe a text shape.
static const TOOL_EVENT DisambiguatePoint
Used for hotkey feedback.
Definition actions.h:360
static const TOOL_EVENT ClearedEvent
Definition actions.h:345
static const TOOL_EVENT InhibitSelectionEditing
Definition actions.h:356
static const TOOL_EVENT SelectedEvent
Definition actions.h:343
static const TOOL_EVENT SelectedItemsModified
Selected items were moved, this can be very high frequency on the canvas, use with care.
Definition actions.h:350
static const TOOL_EVENT UninhibitSelectionEditing
Used to inform tool that it should display the disambiguation menu.
Definition actions.h:357
static const TOOL_EVENT PointSelectedEvent
Definition actions.h:342
static const TOOL_EVENT SelectedItemsMoved
Used to inform tools that the selection should temporarily be non-editable.
Definition actions.h:353
static const TOOL_EVENT UnselectedEvent
Definition actions.h:344
ZONES & Zones()
Definition footprint.h:410
PCB_POINTS & Points()
Definition footprint.h:419
static double GetCoverageArea(const BOARD_ITEM *aItem, const GENERAL_COLLECTOR &aCollector)
const BOX2I GetLayerBoundingBox(const LSET &aLayers) const
Return the bounding box of the footprint on a given set of layers.
std::deque< PAD * > & Pads()
Definition footprint.h:404
PCB_LAYER_ID GetLayer() const override
Return the primary layer this item is on.
Definition footprint.h:449
SHAPE_POLY_SET GetBoundingHull() const
Return a bounding polygon for the shapes and pads in the footprint.
bool IsLocked() const override
Definition footprint.h:680
bool HitTestOnLayer(const VECTOR2I &aPosition, PCB_LAYER_ID aLayer, int aAccuracy=0) const
Test if the point hits one or more of the footprint elements on a given layer.
const KIID_PATH & GetPath() const
Definition footprint.h:496
DRAWINGS & GraphicalItems()
Definition footprint.h:407
const BOX2I GetBoundingBox() const override
Return the orthogonal bounding box of this object for display purposes.
A general implementation of a COLLECTORS_GUIDE.
Definition collectors.h:320
void SetIgnoreBlindBuriedVias(bool ignore)
Definition collectors.h:460
void SetIgnoreTracks(bool ignore)
Definition collectors.h:466
void SetIgnoreFootprintsOnFront(bool ignore)
Definition collectors.h:424
void SetIgnoreFPTextOnFront(bool ignore)
Definition collectors.h:412
void SetIgnoreMicroVias(bool ignore)
Definition collectors.h:463
void SetIgnoreZoneFills(bool ignore)
Definition collectors.h:469
void SetIgnorePadsOnBack(bool ignore)
Definition collectors.h:430
void SetIgnoreFPTextOnBack(bool ignore)
Definition collectors.h:406
void SetLayerVisibleBits(const LSET &aLayerBits)
Definition collectors.h:380
void SetIgnoreThroughVias(bool ignore)
Definition collectors.h:457
void SetIgnoreThroughHolePads(bool ignore)
Definition collectors.h:442
void SetIgnoreFPReferences(bool ignore)
Definition collectors.h:454
void SetIgnoreFPValues(bool ignore)
Definition collectors.h:448
void SetIgnorePadsOnFront(bool ignore)
Definition collectors.h:436
void SetIgnoreFootprintsOnBack(bool ignore)
Definition collectors.h:418
Used when the right click button is pressed, or when the select tool is in effect.
Definition collectors.h:203
void SetGuide(const COLLECTORS_GUIDE *aGuide)
Record which COLLECTORS_GUIDE to use.
Definition collectors.h:287
const COLLECTORS_GUIDE * GetGuide() const
Definition collectors.h:289
static const std::vector< KICAD_T > AllBoardItems
A scan list for all editable board items.
Definition collectors.h:38
void Collect(BOARD_ITEM *aItem, const std::vector< KICAD_T > &aScanList, const VECTOR2I &aRefPos, const COLLECTORS_GUIDE &aGuide)
Scan a BOARD_ITEM using this class's Inspector method, which does the collection.
static const std::vector< KICAD_T > FootprintItems
A scan list for primary footprint items.
Definition collectors.h:110
A color representation with 4 components: red, green, blue, alpha.
Definition color4d.h:101
double a
Alpha component.
Definition color4d.h:393
virtual RENDER_SETTINGS * GetSettings()=0
Return a pointer to current settings that are going to be used when drawing items.
virtual void Update(const VIEW_ITEM *aItem, int aUpdateFlags) const override
For dynamic VIEWs, inform the associated VIEW that the graphical representation of this item has chan...
Definition pcb_view.cpp:87
virtual void Add(VIEW_ITEM *aItem, int aDrawPriority=-1) override
Add a VIEW_ITEM to the view.
Definition pcb_view.cpp:53
virtual void Remove(VIEW_ITEM *aItem) override
Remove a VIEW_ITEM from the view.
Definition pcb_view.cpp:70
Represent a selection area (currently a rectangle) in a VIEW, drawn corner-to-corner between two poin...
void SetMode(SELECTION_MODE aMode)
void SetSubtractive(bool aSubtractive)
SELECTION_MODE GetMode() const
void SetAdditive(bool aAdditive)
void SetPoly(SHAPE_LINE_CHAIN &aPoly)
void SetOrigin(const VECTOR2I &aOrigin)
const BOX2I ViewBBox() const override
Set the origin of the rectangle (the fixed corner)
SHAPE_LINE_CHAIN & GetPoly()
void SetExclusiveOr(bool aExclusiveOr)
void SetEnd(const VECTOR2I &aEnd)
Set the current end of the rectangle (the corner that moves with the cursor.
Container for all the knowledge about how graphical objects are drawn on any output surface/device.
const std::set< int > & GetHighlightNetCodes() const
Return the netcode of currently highlighted net.
const std::set< int > GetHighContrastLayers() const
Returns the set of currently high-contrast layers.
virtual COLOR4D GetColor(const VIEW_ITEM *aItem, int aLayer) const =0
Returns the color that should be used to draw the specific VIEW_ITEM on the specific layer using curr...
void SetHighlight(bool aEnabled, int aNetcode=-1, bool aMulti=false)
Turns on/off highlighting.
virtual void SetCursorPosition(const VECTOR2D &aPosition, bool aWarpView=true, bool aTriggeredByArrows=false, long aArrowCommand=0)=0
Move cursor to the requested position expressed in world coordinates.
virtual void SetAutoPan(bool aEnabled)
Turn on/off auto panning (this feature is used when there is a tool active (eg.
An abstract base class for deriving all objects that can be added to a VIEW.
Definition view_item.h:82
bool IsBOARD_ITEM() const
Definition view_item.h:98
Hold a (potentially large) number of VIEW_ITEMs and renders them on a graphics device provided by the...
Definition view.h:63
double GetScale() const
Definition view.h:281
BOX2D GetViewport() const
Return the current viewport visible area rectangle.
Definition view.cpp:615
virtual void SetScale(double aScale, VECTOR2D aAnchor={ 0, 0 })
Set the scaling factor, zooming around a given anchor point.
Definition view.cpp:655
virtual void Add(VIEW_ITEM *aItem, int aDrawPriority=-1)
Add a VIEW_ITEM to the view.
Definition view.cpp:301
virtual void Remove(VIEW_ITEM *aItem)
Remove a VIEW_ITEM from the view.
Definition view.cpp:416
void UpdateAllLayersColor()
Apply the new coloring scheme to all layers.
Definition view.cpp:862
int Query(const BOX2I &aRect, std::vector< LAYER_ITEM_PAIR > &aResult) const
Find all visible items that touch or are within the rectangle aRect.
Definition view.cpp:505
virtual void Update(const VIEW_ITEM *aItem, int aUpdateFlags) const
For dynamic VIEWs, inform the associated VIEW that the graphical representation of this item has chan...
Definition view.cpp:1852
void Hide(VIEW_ITEM *aItem, bool aHide=true, bool aHideOverlay=false)
Temporarily hide the item in the view (e.g.
Definition view.cpp:1797
bool IsLayerVisible(int aLayer) const
Return information about visibility of a particular layer.
Definition view.h:427
PAINTER * GetPainter() const
Return the painter object used by the view for drawing #VIEW_ITEMS.
Definition view.h:225
void MarkTargetDirty(int aTarget)
Set or clear target 'dirty' flag.
Definition view.h:659
void SetVisible(VIEW_ITEM *aItem, bool aIsVisible=true)
Set the item visibility.
Definition view.cpp:1773
wxString AsString() const
Definition kiid.cpp:423
Definition kiid.h:46
LSEQ is a sequence (and therefore also a set) of PCB_LAYER_IDs.
Definition lseq.h:47
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
static const LSET & AllCuMask()
return AllCuMask( MAX_CU_LAYERS );
Definition lset.cpp:604
LSEQ CuStack() const
Return a sequence of copper layers in starting from the front/top and extending to the back/bottom.
Definition lset.cpp:259
LSEQ Seq(const LSEQ &aSequence) const
Return an LSEQ from the union of this LSET and a desired sequence.
Definition lset.cpp:309
LSEQ SeqStackupTop2Bottom(PCB_LAYER_ID aSelectedLayer=UNDEFINED_LAYER) const
Generate a sequence of layers that represent a top to bottom stack of this set of layers.
Definition lset.cpp:339
static const LSET & AllLayersMask()
Definition lset.cpp:637
static const LSET & PhysicalLayersMask()
Return a mask holding all layers which are physically realized.
Definition lset.cpp:693
bool Contains(PCB_LAYER_ID aLayer) const
See if the layer set contains a PCB layer.
Definition lset.h:63
bool IsExcluded() const
Definition marker_base.h:89
const VECTOR2I & GetPos() const
Definition marker_base.h:79
void ShapeToPolygon(SHAPE_LINE_CHAIN &aPolygon, int aScale=-1) const
Return the shape polygon in internal units in a SHAPE_LINE_CHAIN the coordinates are relatives to the...
Handle the data for a net.
Definition netinfo.h:50
const wxString & GetNetChain() const
Definition netinfo.h:122
PAD * GetTerminalPad(int aIndex) const
Definition netinfo.h:125
REPLACE_TERMINAL_PAD_MENU * m_replaceMenu
ACTION_MENU * create() const override
Return an instance of this class. It has to be overridden in inheriting classes.
Definition pad.h:61
static TOOL_ACTION drag45Degree
static TOOL_ACTION selectNetChain
Select all connections belonging to every net in the current item's net chain.
Definition pcb_actions.h:87
static TOOL_ACTION unrouteSelected
Removes all tracks from the selected items to the first pad.
Definition pcb_actions.h:75
static TOOL_ACTION saveToLinkedDesignBlock
static TOOL_ACTION grabUnconnected
Select and move nearest unconnected footprint from ratsnest of selection.
Definition pcb_actions.h:93
static TOOL_ACTION filterSelection
Filter the items in the current selection (invokes dialog)
static TOOL_ACTION setTerminalPad
static TOOL_ACTION highlightNet
static TOOL_ACTION hideLocalRatsnest
static TOOL_ACTION properties
Activation of the edit tool.
static TOOL_ACTION highlightNetChain
static TOOL_ACTION selectOnSheetFromEeschema
Select all components on sheet from Eeschema crossprobing.
Definition pcb_actions.h:96
static TOOL_ACTION selectConnection
Select tracks between junctions or expands an existing selection to pads or the entire connection.
Definition pcb_actions.h:72
static TOOL_ACTION applyDesignBlockLayout
static TOOL_ACTION dragFreeAngle
static TOOL_ACTION clearHighlight
static TOOL_ACTION selectUnconnected
Select unconnected footprints from ratsnest of selection.
Definition pcb_actions.h:90
static TOOL_ACTION unrouteSegment
Removes track segment from the selected item to the next segment.
Definition pcb_actions.h:78
static TOOL_ACTION moveIndividually
move items one-by-one
static TOOL_ACTION syncSelection
Sets selection to specified items, zooms to fit, if enabled.
Definition pcb_actions.h:62
static TOOL_ACTION selectSameSheet
Select all components on the same sheet as the selected footprint.
Definition pcb_actions.h:99
static TOOL_ACTION selectNet
Select all connections belonging to a single net.
Definition pcb_actions.h:81
static TOOL_ACTION move
move or drag an item
static TOOL_ACTION syncSelectionWithNets
Sets selection to specified items with connected nets, zooms to fit, if enabled.
Definition pcb_actions.h:65
static TOOL_ACTION deselectNet
Remove all connections belonging to a single net from the active selection.
Definition pcb_actions.h:84
static TOOL_ACTION placeLinkedDesignBlock
static TOOL_ACTION selectOnSchematic
Select symbols/pins on schematic corresponding to selected footprints/pads.
Common, abstract interface for edit frames.
Base PCB main window class for Pcbnew, Gerbview, and CvPcb footprint viewer.
const PCB_DISPLAY_OPTIONS & GetDisplayOptions() const
Display options control the way tracks, vias, outlines and other things are shown (for instance solid...
double m_TrackOpacity
Opacity override for all tracks.
double m_FilledShapeOpacity
Opacity override for graphic shapes.
double m_ZoneOpacity
Opacity override for filled zone areas.
double m_ImageOpacity
Opacity override for user images.
double m_PadOpacity
Opacity override for SMD pads and PTHs.
double m_ViaOpacity
Opacity override for all types of via.
ZONE_DISPLAY_MODE m_ZoneDisplayMode
virtual KIGFX::PCB_VIEW * GetView() const override
Return a pointer to the #VIEW instance used in the panel.
A drill chart placed on the board, kept in step with the holes.
bool IsDataRow(int aRow) const
True for a row that reports a drill group, false for the title, heading and totals.
The main frame for Pcbnew.
bool IsReference() const
Definition pcb_field.h:76
bool IsValue() const
Definition pcb_field.h:77
virtual ACTION_MENU * GetChildContextMenu(TOOL_INTERACTIVE *aTool) const
Get a context menu when interacting with a generator child.
virtual bool ChildrenAreIndividuallySelectable() const
A set of BOARD_ITEMs (i.e., without duplicates).
Definition pcb_group.h:51
static bool WithinScope(BOARD_ITEM *aItem, PCB_GROUP *aScope, bool isFootprintEditor)
static EDA_GROUP * TopLevelGroup(BOARD_ITEM *aItem, EDA_GROUP *aScope, bool isFootprintEditor)
std::unordered_set< BOARD_ITEM * > GetBoardItems() const
Tool that displays edit points allowing to modify items by dragging the points.
bool HasPoint()
Indicate the cursor is over an edit point.
A PCB_POINT is a 0-dimensional point that is used to mark a position on a PCB, or more usually a foot...
Definition pcb_point.h:39
Private implementation of firewalled private data.
DIALOG_FILTER_SELECTION::OPTIONS m_filterOpts
The selection tool: currently supports:
void highlight(EDA_ITEM *aItem, int aHighlightMode, SELECTION *aGroup=nullptr) override
Highlight the item visually.
int syncSelectionWithNets(const TOOL_EVENT &aEvent)
int SelectTable(const TOOL_EVENT &aEvent)
Clear current selection event handler.
std::function< void(KIGFX::PREVIEW::SELECTION_AREA &aArea)> AREA_PREVIEW
Called with the drag box every time it changes, before anything is selected.
PCB_BASE_FRAME * frame() const
int syncSelection(const TOOL_EVENT &aEvent)
int selectNet(const TOOL_EVENT &aEvent)
Select all copper connections belonging to the same net(s) as the items in the selection.
int filterSelection(const TOOL_EVENT &aEvent)
Return true if the given item passes the current SELECTION_FILTER_OPTIONS.
void Reset(RESET_REASON aReason) override
Bring the tool to a known, initial state.
void ZoomFitCrossProbeBBox(const BOX2I &bbox)
void EnterGroup() override
Enter the group at the head of the current selection.
void doSyncSelection(const std::vector< BOARD_ITEM * > &aItems, bool aWithNets)
Invoke filter dialog and modify current selection.
SELECTION_MODE m_selectionMode
void GuessSelectionCandidates(GENERAL_COLLECTOR &aCollector, const VECTOR2I &aWhere) const
Try to guess best selection candidates in case multiple items are clicked, by doing some brain-dead h...
int selectNetChain(const TOOL_EVENT &aEvent)
Select all copper connections belonging to every net in the selected item's net chain.
bool DragSelectionArea(TOOL_INTERACTIVE &aTool, AREA_PREVIEW aPreview=nullptr)
Drive the rectangle drag-selection loop from another tool's event loop.
void ExitGroup(bool aSelectGroup=false) override
Leave the currently-entered group.
bool isExpandableGraphicShape(const EDA_ITEM *aItem) const
bool toggleTableCellSelection(const VECTOR2I &aPosition)
When the user Ctrl-clicks inside a PCB_TABLE whose cells are already in the selection,...
static bool HasLockedDescendant(const BOARD_ITEM *aItem)
int disambiguateCursor(const TOOL_EVENT &aEvent)
Handle disambiguation actions including displaying the menu.
int SelectPolyArea(const TOOL_EVENT &aEvent)
Handles drawing a lasso selection area that allows multiple items to be selected simultaneously.
void FilterCollectorForMarkers(GENERAL_COLLECTOR &aCollector) const
Drop any PCB_MARKERs from the collector.
int UnselectAll(const TOOL_EVENT &aEvent)
Change the selection mode.
void unhighlightInternal(EDA_ITEM *aItem, int aHighlightMode, bool aUsingOverlay)
bool selectionContains(const VECTOR2I &aPoint) const
void select(EDA_ITEM *aItem) override
Take necessary action mark an item as selected.
bool selectCursor(bool aForceSelect=false, CLIENT_SELECTION_FILTER aClientFilter=nullptr)
Select an item under the cursor unless there is something already selected.
bool collectAtPoint(const VECTOR2I &aWhere, GENERAL_COLLECTOR &aCollector, POINT_COLLECT &aOptions, CLIENT_SELECTION_FILTER aClientFilter)
Collect the items at aWhere and narrow them the way a click does.
int SetSelectPoly(const TOOL_EVENT &aEvent)
int SetSelectRect(const TOOL_EVENT &aEvent)
int SelectColumns(const TOOL_EVENT &aEvent)
std::unique_ptr< PRIV > m_priv
PCB_TABLECELL * singleSelectedCell() const
int unrouteSelected(const TOOL_EVENT &aEvent)
Unroute the selected board connected items.
int unrouteSegment(const TOOL_EVENT &aEvent)
Unroute the selected track connected item.
SELECTION & selection() override
Return a reference to the selection.
int grabUnconnected(const TOOL_EVENT &aEvent)
Select and move other nearest footprint unconnected on same net as selected items.
PCB_SELECTION & RequestSelection(CLIENT_SELECTION_FILTER aClientFilter)
Return the current selection, filtered according to aClientFilter.
static bool isWithinEnteredGroup(BOARD_ITEM *aItem, PCB_GROUP *aEnteredGroup, bool aIsFootprintEditor)
True if aItem may be selected while aEnteredGroup is entered (24967).
bool ReportFilteredLockedItems()
If the most recent FilterCollectorForLockedItems call filtered a locked item, show an InfoBar warning...
void FilterCollectorForFreePads(GENERAL_COLLECTOR &aCollector, bool aForcePromotion=false) const
Check the "allow free pads" setting and if disabled, replace any pads in the collector with their par...
bool itemPassesFilter(BOARD_ITEM *aItem, bool aMultiSelect, PCB_SELECTION_FILTER_OPTIONS *aRejected=nullptr)
const GENERAL_COLLECTORS_GUIDE getCollectorsGuide() const
bool Selectable(const BOARD_ITEM *aItem, bool checkVisibilityOnly=false) const
std::vector< BOARD_ITEM * > CollectPoint(const VECTOR2I &aWhere, CLIENT_SELECTION_FILTER aClientFilter=nullptr)
The items a click at aWhere would consider, best first.
bool selectPoint(const VECTOR2I &aWhere, bool aOnDrag=false, bool *aSelectionCancelledFlag=nullptr, CLIENT_SELECTION_FILTER aClientFilter=nullptr)
Select an item pointed by the parameter aWhere.
KIGFX::PCB_VIEW * view() const
void selectAllItemsOnSheet(wxString &aSheetPath)
Select all items with the given sheet timestamp/UUID name (the sheet path).
void FilterCollectorForHierarchy(GENERAL_COLLECTOR &aCollector, bool aMultiselect) const
In general we don't want to select both a parent and any of it's children.
void SelectMultiple(KIGFX::PREVIEW::SELECTION_AREA &aArea, bool aSubtractive=false, bool aExclusiveOr=false)
Selects multiple PCB items within a specified area.
void setTransitions() override
Zoom the screen to center and fit the current selection.
PCB_BASE_EDIT_FRAME * editFrame() const
int expandConnection(const TOOL_EVENT &aEvent)
Expand the current connected-item selection to the next boundary (junctions, pads,...
bool selectChartRow(const VECTOR2I &aPosition)
Select the drill chart row under a point, when the chart itself is what is selected.
int selectUnconnected(const TOOL_EVENT &aEvent)
Select nearest unconnected footprints on same net as selected items.
virtual bool ctrlClickHighlights() override
Determine if ctrl-click is highlight net or XOR selection.
int selectSheetContents(const TOOL_EVENT &aEvent)
Select all footprints belonging to same hierarchical sheet as the selected footprint (same sheet path...
int SelectRows(const TOOL_EVENT &aEvent)
int selectSameSheet(const TOOL_EVENT &aEvent)
Set selection to items passed by parameter and connected nets (optionally).
void zoomFitSelection()
Zoom the screen to fit the bounding box for cross probing/selection sync.
std::vector< BOARD_ITEM * > CollectMultiple(KIGFX::PREVIEW::SELECTION_AREA &aArea)
The items a drag over aArea would take, in the order SelectMultiple() would take them.
void selectAllConnectedTracks(const std::vector< BOARD_CONNECTED_ITEM * > &aStartItems, STOP_CONDITION aStopCondition)
Select connected tracks and vias.
int CursorSelection(const TOOL_EVENT &aEvent)
int ClearSelection(const TOOL_EVENT &aEvent)
void collectTableCellsAt(const VECTOR2I &aPosition, GENERAL_COLLECTOR &aCollector)
Collect PCB_TABLECELL items at aPosition into aCollector, scoped to either the active footprint (in t...
void highlightInternal(EDA_ITEM *aItem, int aHighlightMode, bool aUsingOverlay)
PCB_BASE_FRAME * m_frame
PCB_SELECTION_FILTER_OPTIONS m_filter
void initializeTableCellSelectionState(PCB_TABLE *aTable)
Mark the existing selection state of table cells so that drag- and shift-click range selection can pr...
PCB_SELECTION & GetSelection()
@ STOP_AT_SEGMENT
Stop when reaching a segment (next track/arc/via).
@ STOP_AT_PAD
Stop when reaching a pad.
@ STOP_NEVER
Select the entire net.
@ STOP_AT_JUNCTION
Stop at any place where more than two traces meet.
int Main(const TOOL_EVENT &aEvent)
The main loop.
void RebuildSelection()
Rebuild the selection from the EDA_ITEMs' selection flags.
void pruneObscuredSelectionCandidates(GENERAL_COLLECTOR &aCollector) const
void OnIdle(wxIdleEvent &aEvent)
PCB_DRAW_PANEL_GAL * canvas() const
void FilterCollectorForFootprints(GENERAL_COLLECTOR &aCollector, const VECTOR2I &aWhere) const
Drop footprints that are not directly selected.
PCB_TABLECELL * m_previousFirstCell
PCB_SELECTION m_blockedSelection
void FindItem(BOARD_ITEM *aItem)
Take necessary actions to mark an item as found.
void FilterCollectorForLockedItems(GENERAL_COLLECTOR &aCollector)
In the PCB editor strip out any locked items unless the OverrideLocks checkbox is set.
int SelectRectArea(const TOOL_EVENT &aEvent)
Handles drawing a selection box that allows multiple items to be selected simultaneously.
int updateSelection(const TOOL_EVENT &aEvent)
Event handler to update the selection VIEW_ITEM.
bool Init() override
Init() is called once upon a registration of the tool.
KIGFX::VIEW_GROUP m_enteredGroupOverlay
void selectConnections(const std::vector< BOARD_ITEM * > &aItems)
int hitTestDistance(const VECTOR2I &aWhere, BOARD_ITEM *aItem, int aMaxDistance) const
bool extendTableCellSelectionTo(const VECTOR2I &aPosition)
If the current selection holds one or more cells from a single PCB_TABLE and the cursor is over anoth...
void selectAllConnectedShapes(const std::vector< PCB_SHAPE * > &aStartItems)
Select all non-closed shapes that are graphically connected to the given start items.
void SelectAllItemsOnNet(int aNetCode, bool aSelect=true)
Select all items with the given net code.
void FilterCollectorForTableCells(GENERAL_COLLECTOR &aCollector) const
Promote any table cell selections to the whole table.
void unselect(EDA_ITEM *aItem) override
Take necessary action mark an item as unselected.
int SelectAll(const TOOL_EVENT &aEvent)
Unselect all items on the board.
void FilterCollectedItems(GENERAL_COLLECTOR &aCollector, bool aMultiSelect, PCB_SELECTION_FILTER_OPTIONS *aRejected=nullptr)
Apply the SELECTION_FITLER_OPTIONS to the collector.
void selectCellsBetween(const VECTOR2D &aStart, const VECTOR2D &aEnd, PCB_TABLE *aTable)
Select table cells contained within the rectangle defined by two corner points, combining the result ...
bool selectTableCells(PCB_TABLE *aTable)
void unhighlight(EDA_ITEM *aItem, int aHighlightMode, SELECTION *aGroup=nullptr) override
Unhighlight the item visually.
VECTOR2I GetCenter() const override
This defaults to the center of the bounding box if not overridden.
Definition pcb_shape.h:78
virtual LSET GetLayerSet() const override
Return a std::bitset of all layers on which the item physically resides.
std::vector< VECTOR2I > GetConnectionPoints() const
int GetRowSpan() const
int GetColSpan() const
int GetRow() const
std::vector< PCB_TABLECELL * > GetCells() const
Definition pcb_table.h:160
virtual LSET GetLayerSet() const override
Return a std::bitset of all layers on which the item physically resides.
const VECTOR2I & GetStart() const
Definition pcb_track.h:93
const VECTOR2I & GetEnd() const
Definition pcb_track.h:90
VECTOR2I GetPosition() const override
Definition pcb_track.h:580
int GetWidth() const override
virtual LSET GetLayerSet() const override
Return a std::bitset of all layers on which the item physically resides.
A small class to help profiling.
Definition profile.h:46
void Start()
Start or restart the counter.
Definition profile.h:74
double msecs(bool aSinceLast=false)
Definition profile.h:147
ACTION_MENU * create() const override
Return an instance of this class. It has to be overridden in inheriting classes.
OPT_TOOL_EVENT eventHandler(const wxMenuEvent &aEvent) override
Event handler stub.
void update() override
Update menu state stub.
Describe ratsnest for a single net.
const std::vector< CN_EDGE > & GetEdges() const
bool RoutingInProgress()
Returns whether routing is currently active.
Definition seg.h:38
static SEG::ecoord Square(int a)
Definition seg.h:119
static SELECTION_CONDITION HasType(KICAD_T aType)
Create a functor that tests if among the selected items there is at least one of a given type.
static bool NotEmpty(const SELECTION &aSelection)
Test if there are any items selected.
static SELECTION_CONDITION MoreThan(int aNumber)
Create a functor that tests if the number of selected items is greater than the value given as parame...
static SELECTION_CONDITION Count(int aNumber)
Create a functor that tests if the number of selected items is equal to the value given as parameter.
static SELECTION_CONDITION OnlyTypes(std::vector< KICAD_T > aTypes)
Create a functor that tests if the selected items are only of given types.
bool m_multiple
Multiple selection mode is active.
int RemoveItemFromSel(const TOOL_EVENT &aEvent)
bool doSelectionMenu(COLLECTOR *aCollector)
wxTimer m_disambiguateTimer
Timer to show the disambiguate menu.
bool m_drag_additive
Add multiple items to selection.
bool m_exclusive_or
Items' selection state should be toggled.
int AddItemsToSel(const TOOL_EVENT &aEvent)
int AddItemToSel(const TOOL_EVENT &aEvent)
int UpdateMenu(const TOOL_EVENT &aEvent)
Update a menu's state based on the current selection.
void setModifiersState(bool aShiftState, bool aCtrlState, bool aAltState)
Set the configuration of m_additive, m_subtractive, m_exclusive_or, m_skip_heuristics from the state ...
VECTOR2I m_originalCursor
Location of original cursor when starting click.
int SelectionMenu(const TOOL_EVENT &aEvent)
Show a popup menu to trim the COLLECTOR passed as aEvent's parameter down to a single item.
int RemoveItemsFromSel(const TOOL_EVENT &aEvent)
bool m_subtractive
Items should be removed from selection.
SELECTION_TOOL(const std::string &aName)
bool m_highlight_modifier
Select highlight net on left click.
bool m_skip_heuristics
Show disambiguation menu for all items under the cursor rather than trying to narrow them down first ...
int ReselectItem(const TOOL_EVENT &aEvent)
bool m_drag_subtractive
Remove multiple from selection.
bool m_additive
Items should be added to sel (instead of replacing).
bool hasModifier()
True if a selection modifier is enabled, false otherwise.
bool m_canceledMenu
Sets to true if the disambiguation menu was canceled.
void onDisambiguationExpire(wxTimerEvent &aEvent)
Start the process to show our disambiguation menu once the user has kept the mouse down for the minim...
virtual void Add(EDA_ITEM *aItem)
A null aItem is ignored; the selection never holds null members.
Definition selection.cpp:38
virtual void Remove(EDA_ITEM *aItem)
Definition selection.cpp:60
EDA_ITEM * Front() const
Definition selection.h:176
void ClearReferencePoint()
bool Empty() const
Checks if there is anything selected.
Definition selection.h:114
ACTION_MENU * create() const override
Return an instance of this class. It has to be overridden in inheriting classes.
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
void Move(const VECTOR2I &aVector) override
void GenerateBBoxCache() const
void SetClosed(bool aClosed)
Mark the line chain as closed (i.e.
int PointCount() const
Return the number of points (vertices) in this line chain.
virtual bool Collide(const VECTOR2I &aP, int aClearance=0, int *aActual=nullptr, VECTOR2I *aLocation=nullptr) const override
Check if point aP lies closer to us than aClearance.
double Area(bool aAbsolute=true) const
Return the area of this chain.
virtual size_t GetPointCount() const override
void Append(int aX, int aY, bool aAllowDuplication=false)
Append a new point at the end of the line chain.
const VECTOR2I & CLastPoint() const
Return the last point in the line chain.
void Remove(int aStartIndex, int aEndIndex)
Remove the range of points [start_index, end_index] from the line chain.
bool Collide(const SHAPE *aShape, int aClearance=0, int *aActual=nullptr, VECTOR2I *aLocation=nullptr) const override
Check if the boundary of shape (this) lies closer to the shape aShape than aClearance,...
virtual bool Collide(const VECTOR2I &aP, int aClearance=0, int *aActual=nullptr, VECTOR2I *aLocation=nullptr) const
Check if the boundary of shape (this) lies closer to the point aP than aClearance,...
Definition shape.h:179
bool ToolStackIsEmpty()
Represent a single user action.
T * getEditFrame() const
Return the application window object, casted to requested user type.
Definition tool_base.h:182
T * getModel() const
Return the model object if it matches the requested type.
Definition tool_base.h:195
KIGFX::VIEW_CONTROLS * getViewControls() const
Return the instance of VIEW_CONTROLS object used in the application.
Definition tool_base.cpp:40
TOOL_MANAGER * m_toolMgr
Definition tool_base.h:220
KIGFX::VIEW * getView() const
Returns the instance of #VIEW object used in the application.
Definition tool_base.cpp:34
bool IsToolActive() const
Definition tool_base.cpp:28
RESET_REASON
Determine the reason of reset for a tool.
Definition tool_base.h:74
@ REDRAW
Full drawing refresh.
Definition tool_base.h:79
@ MODEL_RELOAD
Model changes (the sheet for a schematic)
Definition tool_base.h:76
Generic, UI-independent tool event.
Definition tool_event.h:167
void SetParameter(T aParam)
Set a non-standard parameter assigned to the event.
Definition tool_event.h:524
bool IsAction(const TOOL_ACTION *aAction) const
Test if the event contains an action issued upon activation of the given TOOL_ACTION.
T Parameter() const
Return a parameter assigned to the event.
Definition tool_event.h:469
void SetPassEvent(bool aPass=true)
Definition tool_event.h:252
void SetContextMenu(ACTION_MENU *aMenu, CONTEXT_MENU_TRIGGER aTrigger=CMENU_BUTTON)
Assign a context menu and tells when it should be activated.
void Go(int(T::*aStateFunc)(const TOOL_EVENT &), const TOOL_EVENT_LIST &aConditions=TOOL_EVENT(TC_ANY, TA_ANY))
Define which state (aStateFunc) to go when a certain event arrives (aConditions).
std::unique_ptr< TOOL_MENU > m_menu
The functions below are not yet implemented - their interface may change.
TOOL_INTERACTIVE(TOOL_ID aId, const std::string &aName)
Create a tool with given id & name.
TOOL_EVENT * Wait(const TOOL_EVENT_LIST &aEventList=TOOL_EVENT(TC_ANY, TA_ANY))
Suspend execution of the tool until an event specified in aEventList arrives.
Master controller class:
TOOLS_HOLDER * GetToolHolder() const
Handle a list of polygons defining a copper zone.
Definition zone.h:70
bool GetIsRuleArea() const
Accessors to parameters used in Rule Area zones:
Definition zone.h:807
bool HitTestForCorner(const VECTOR2I &refPos, int aAccuracy, SHAPE_POLY_SET::VERTEX_INDEX *aCornerHit=nullptr) const
Test if the given VECTOR2I is near a corner.
Definition zone.cpp:930
bool HitTestForEdge(const VECTOR2I &refPos, int aAccuracy, SHAPE_POLY_SET::VERTEX_INDEX *aCornerHit=nullptr) const
Test if the given VECTOR2I is near a segment defined by 2 corners.
Definition zone.cpp:941
bool IsTeardropArea() const
Definition zone.h:782
virtual LSET GetLayerSet() const override
Return a std::bitset of all layers on which the item physically resides.
Definition zone.h:133
bool GetDoNotAllowZoneFills() const
Definition zone.h:817
A type-safe container of any type.
Definition ki_any.h:92
#define EXCLUDE_ZONES
#define IGNORE_NETS
Function GetConnectedItems() Returns a list of items connected to a source item aItem.
KICURSOR
Definition cursors.h:40
@ SUBTRACT
Definition cursors.h:68
@ SELECT_WINDOW
Definition cursors.h:82
@ SELECT_LASSO
Definition cursors.h:84
@ MOVING
Definition cursors.h:44
@ ARROW
Definition cursors.h:42
#define DEFAULT_TEXT_SIZE
Ratio of the font height to the baseline of the text above the wire.
static bool empty(const wxTextEntryBase *aCtrl)
#define _(s)
@ RECURSE
Definition eda_item.h:51
std::function< INSPECT_RESULT(EDA_ITEM *aItem, void *aTestData) > INSPECTOR_FUNC
Used to inspect and possibly collect the (search) results of iterating over a list or tree of KICAD_T...
Definition eda_item.h:88
#define BRIGHTENED
item is drawn with a bright contour
#define IS_NEW
New item, just created.
#define SELECTED
Item was manually selected by the user.
#define ENTERED
indicates a group has been entered
#define SKIP_STRUCT
flag indicating that the structure should be ignored
#define CANDIDATE
flag indicating that the structure is connected
#define IS_MOVING
Item being moved.
@ SEGMENT
Definition eda_shape.h:56
@ FRAME_PCB_EDITOR
Definition frame_type.h:38
@ FRAME_FOOTPRINT_VIEWER
Definition frame_type.h:41
@ FRAME_FOOTPRINT_EDITOR
Definition frame_type.h:39
a few functions useful in geometry calculations.
double m_PcbSelectionVisibilityRatio
Board object selection visibility limit.
KIID niluuid(0)
@ LAYER_POINTS
PCB reference/manual snap points visibility.
Definition layer_ids.h:317
@ LAYER_FOOTPRINTS_FR
Show footprints on front.
Definition layer_ids.h:255
@ LAYER_DRAW_BITMAPS
Draw images.
Definition layer_ids.h:280
@ LAYER_FP_REFERENCES
Show footprints references (when texts are visible).
Definition layer_ids.h:262
@ LAYER_DRC_EXCLUSION
Layer for DRC markers which have been individually excluded.
Definition layer_ids.h:300
@ LAYER_ZONES
Control for copper zone opacity/visibility (color ignored).
Definition layer_ids.h:291
@ LAYER_PADS
Meta control for all pads opacity/visibility (color ignored).
Definition layer_ids.h:288
@ LAYER_TRACKS
Definition layer_ids.h:263
@ LAYER_FP_TEXT
Definition layer_ids.h:236
@ LAYER_FOOTPRINTS_BK
Show footprints on back.
Definition layer_ids.h:256
@ LAYER_FP_VALUES
Show footprints values (when texts are visible).
Definition layer_ids.h:259
@ LAYER_SUBGRIDS
Routing/placement subgrids (PCB_GRID_ITEM) visibility and color.
Definition layer_ids.h:323
@ LAYER_VIAS
Meta control for all vias opacity/visibility.
Definition layer_ids.h:228
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
@ F_CrtYd
Definition layer_ids.h:112
@ Edge_Cuts
Definition layer_ids.h:108
@ F_SilkS
Definition layer_ids.h:96
@ B_CrtYd
Definition layer_ids.h:111
@ UNDEFINED_LAYER
Definition layer_ids.h:57
@ B_SilkS
Definition layer_ids.h:97
@ PCB_LAYER_ID_COUNT
Definition layer_ids.h:167
PCB_LAYER_ID ToLAYER_ID(int aLayer)
Definition lset.cpp:750
This file contains miscellaneous commonly used macros and functions.
#define KI_FALLTHROUGH
The KI_FALLTHROUGH macro is to be used when switch statement cases should purposely fallthrough from ...
Definition macros.h:79
MOUSE_DRAG_ACTION
bool BoxHitTest(const VECTOR2I &aHitPoint, const BOX2I &aHittee, int aAccuracy)
Perform a point-to-box hit test.
@ REPAINT
Item needs to be redrawn.
Definition view_item.h:54
@ TARGET_OVERLAY
Items that may change while the view stays the same (noncached)
Definition definitions.h:35
STL namespace.
@ NPTH
like PAD_PTH, but not plated mechanical use only, no connection allowed
Definition padstack.h:102
@ PTH
Plated through hole pad.
Definition padstack.h:97
Class to handle a set of BOARD_ITEMs.
void connectedItemFilter(const VECTOR2I &, GENERAL_COLLECTOR &aCollector, PCB_SELECTION_TOOL *sTool)
static void passEvent(TOOL_EVENT *const aEvent, const TOOL_ACTION *const aAllowedActions[])
static bool itemIsIncludedByFilter(const BOARD_ITEM &aItem, const BOARD &aBoard, const DIALOG_FILTER_SELECTION::OPTIONS &aFilterOptions)
Determine if an item is included by the filter specified.
@ ID_REPLACE_TERMINAL_PAD_A
@ ID_REPLACE_TERMINAL_PAD_B
TRACK_DRAG_ACTION
CITER next(CITER it)
Definition ptree.cpp:120
Class that computes missing connections on a PCB.
static float distance(const SFVEC2UI &a, const SFVEC2UI &b)
static std::vector< KICAD_T > tableCellTypes
const TOOL_ACTION * allowedActions[]
static void passEvent(TOOL_EVENT *const aEvent, const TOOL_ACTION *const aAllowedActions[])
std::function< bool(const SELECTION &)> SELECTION_CONDITION
Functor type that checks a specific condition for selected items.
SELECTION_MODE
const int scale
Struct that will be set with the result of the user choices in the dialog.
const BOARD_ITEM * m_Item
This file contains data structures that are saved in the project file or project local settings file ...
bool otherItems
Anything not fitting one of the above categories.
bool graphics
Graphic lines, shapes, polygons.
bool footprints
Allow selecting entire footprints.
bool text
Text (free or attached to a footprint)
bool lockedItems
Allow selecting locked items.
What one point collection needs beyond the point, and the one count it reports back.
bool m_SelectedOnly
Subtracting takes only selected.
size_t m_PreFilterCount
Count before that filter, out.
bool m_OnDrag
Locked items cannot be dragged.
PCB_SELECTION_FILTER_OPTIONS * m_Rejected
What the Selection Filter took.
KIBIS top(path, &reporter)
int radius
VECTOR2I end
int actual
const int accuracy
int delta
@ TA_MOUSE_UP
Definition tool_event.h:65
@ TA_MOUSE_WHEEL
Definition tool_event.h:69
@ CMENU_NOW
Right now (after TOOL_INTERACTIVE::SetContextMenu).
Definition tool_event.h:152
std::optional< TOOL_EVENT > OPT_TOOL_EVENT
Definition tool_event.h:637
@ MD_ALT
Definition tool_event.h:141
@ MD_CTRL
Definition tool_event.h:140
@ MD_SHIFT
Definition tool_event.h:139
@ TC_ANY
Definition tool_event.h:56
@ BUT_MIDDLE
Definition tool_event.h:130
@ BUT_LEFT
Definition tool_event.h:128
@ BUT_RIGHT
Definition tool_event.h:129
wxLogTrace helper definitions.
KICAD_T
The set of class identification values stored in EDA_ITEM::m_structType.
Definition typeinfo.h:70
@ PCB_CONSTRAINT_T
a geometric constraint between board items
Definition typeinfo.h:237
@ PCB_SHAPE_T
class PCB_SHAPE, a segment not on copper layers
Definition typeinfo.h:80
@ PCB_DIM_ORTHOGONAL_T
class PCB_DIM_ORTHOGONAL, a linear dimension constrained to x/y
Definition typeinfo.h:98
@ PCB_DIM_LEADER_T
class PCB_DIM_LEADER, a leader dimension (graphic item)
Definition typeinfo.h:95
@ PCB_GENERATOR_T
class PCB_GENERATOR, generator on a layer
Definition typeinfo.h:83
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition typeinfo.h:89
@ TYPE_NOT_INIT
Definition typeinfo.h:73
@ PCB_DRILL_MAP_T
class PCB_DRILL_MAP, drill symbols drawn at the holes
Definition typeinfo.h:240
@ PCB_DIM_CENTER_T
class PCB_DIM_CENTER, a center point marking (graphic item)
Definition typeinfo.h:96
@ PCB_GROUP_T
class PCB_GROUP, a set of BOARD_ITEMs
Definition typeinfo.h:103
@ PCB_TEXTBOX_T
class PCB_TEXTBOX, wrapped text on a layer
Definition typeinfo.h:85
@ PCB_ZONE_T
class ZONE, a copper pour area
Definition typeinfo.h:100
@ PCB_TEXT_T
class PCB_TEXT, text on a layer
Definition typeinfo.h:84
@ PCB_REFERENCE_IMAGE_T
class PCB_REFERENCE_IMAGE, bitmap on a layer
Definition typeinfo.h:81
@ PCB_FIELD_T
class PCB_FIELD, text associated with a footprint property
Definition typeinfo.h:82
@ NOT_USED
the 3d code uses this value
Definition typeinfo.h:71
@ PCB_MARKER_T
class PCB_MARKER, a marker used to show something
Definition typeinfo.h:91
@ PCB_BARCODE_T
class PCB_BARCODE, a barcode (graphic item)
Definition typeinfo.h:93
@ PCB_TARGET_T
class PCB_TARGET, a target (graphic item)
Definition typeinfo.h:99
@ PCB_TABLECELL_T
class PCB_TABLECELL, PCB_TEXTBOX for use in tables
Definition typeinfo.h:87
@ PCB_FOOTPRINT_T
class FOOTPRINT, a footprint
Definition typeinfo.h:78
@ PCB_GRID_ITEM_T
a subgrid placed on a board
Definition typeinfo.h:238
@ PCB_DIM_ALIGNED_T
class PCB_DIM_ALIGNED, a linear dimension (graphic item)
Definition typeinfo.h:94
@ PCB_PAD_T
class PAD, a pad in a footprint
Definition typeinfo.h:79
@ PCB_ARC_T
class PCB_ARC, an arc track segment on a copper layer
Definition typeinfo.h:90
@ PCB_TABLE_T
class PCB_TABLE, table of PCB_TABLECELLs
Definition typeinfo.h:86
@ PCB_NETINFO_T
class NETINFO_ITEM, a description of a net
Definition typeinfo.h:102
@ PCB_POINT_T
class PCB_POINT, a 0-dimensional point
Definition typeinfo.h:105
@ PCB_TRACE_T
class PCB_TRACK, a track segment (segment on a copper layer)
Definition typeinfo.h:88
@ PCB_DIM_RADIAL_T
class PCB_DIM_RADIAL, a radius or diameter dimension
Definition typeinfo.h:97
@ PCB_DRILL_CHART_T
class PCB_DRILL_CHART, a live drill chart derived from PCB_TABLE
Definition typeinfo.h:239
Casted dyn_cast(From aObject)
A lightweight dynamic downcast.
Definition typeinfo.h:55
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683
VECTOR2< double > VECTOR2D
Definition vector2d.h:682
VECTOR2D ToVECTOR2D(const wxPoint &aPoint)
Definition vector2wx.h:36