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