You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

4453 lines
138 KiB

4 months ago
5 years ago
5 years ago
3 years ago
6 months ago
11 years ago
9 years ago
9 years ago
11 years ago
11 years ago
10 years ago
11 years ago
6 months ago
  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 <tomasz.wlostowski@cern.ch>
  7. * @author Maciej Suminski <maciej.suminski@cern.ch>
  8. *
  9. * This program is free software; you can redistribute it and/or
  10. * modify it under the terms of the GNU General Public License
  11. * as published by the Free Software Foundation; either version 2
  12. * of the License, or (at your option) any later version.
  13. *
  14. * This program is distributed in the hope that it will be useful,
  15. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. * GNU General Public License for more details.
  18. *
  19. * You should have received a copy of the GNU General Public License
  20. * along with this program; if not, you may find one here:
  21. * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  22. * or you may search the http://www.gnu.org website for the version 2 license,
  23. * or you may write to the Free Software Foundation, Inc.,
  24. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  25. */
  26. #include <limits>
  27. #include <cmath>
  28. #include <functional>
  29. #include <stack>
  30. using namespace std::placeholders;
  31. #include <advanced_config.h>
  32. #include <macros.h>
  33. #include <core/kicad_algo.h>
  34. #include <board.h>
  35. #include <board_design_settings.h>
  36. #include <board_item.h>
  37. #include <pcb_reference_image.h>
  38. #include <pcb_track.h>
  39. #include <footprint.h>
  40. #include <pad.h>
  41. #include <pcb_group.h>
  42. #include <pcb_shape.h>
  43. #include <pcb_text.h>
  44. #include <pcb_textbox.h>
  45. #include <pcb_table.h>
  46. #include <pcb_tablecell.h>
  47. #include <pcb_marker.h>
  48. #include <pcb_generator.h>
  49. #include <zone.h>
  50. #include <collectors.h>
  51. #include <dialog_filter_selection.h>
  52. #include <dialogs/dialog_locked_items_query.h>
  53. #include <class_draw_panel_gal.h>
  54. #include <view/view_controls.h>
  55. #include <gal/painter.h>
  56. #include <router/router_tool.h>
  57. #include <pcbnew_settings.h>
  58. #include <tool/tool_event.h>
  59. #include <tool/tool_manager.h>
  60. #include <tools/tool_event_utils.h>
  61. #include <tools/pcb_point_editor.h>
  62. #include <tools/pcb_selection_tool.h>
  63. #include <tools/pcb_actions.h>
  64. #include <tools/board_inspection_tool.h>
  65. #include <connectivity/connectivity_data.h>
  66. #include <ratsnest/ratsnest_data.h>
  67. #include <footprint_viewer_frame.h>
  68. #include <geometry/geometry_utils.h>
  69. #include <wx/event.h>
  70. #include <wx/timer.h>
  71. #include <wx/log.h>
  72. #include <wx/debug.h>
  73. #include <core/profile.h>
  74. #include <math/vector2wx.h>
  75. struct LAYER_OPACITY_ITEM
  76. {
  77. PCB_LAYER_ID m_Layer;
  78. double m_Opacity;
  79. const BOARD_ITEM* m_Item;;
  80. };
  81. class SELECT_MENU : public ACTION_MENU
  82. {
  83. public:
  84. SELECT_MENU() :
  85. ACTION_MENU( true )
  86. {
  87. SetTitle( _( "Select" ) );
  88. Add( PCB_ACTIONS::filterSelection );
  89. AppendSeparator();
  90. Add( PCB_ACTIONS::selectConnection );
  91. Add( PCB_ACTIONS::selectNet );
  92. // This could be enabled if we have better logic for picking the target net with the mouse
  93. // Add( PCB_ACTIONS::deselectNet );
  94. Add( PCB_ACTIONS::selectSameSheet );
  95. Add( PCB_ACTIONS::selectOnSchematic );
  96. Add( PCB_ACTIONS::selectUnconnected );
  97. Add( PCB_ACTIONS::grabUnconnected );
  98. }
  99. private:
  100. ACTION_MENU* create() const override
  101. {
  102. return new SELECT_MENU();
  103. }
  104. };
  105. /**
  106. * Private implementation of firewalled private data.
  107. */
  108. class PCB_SELECTION_TOOL::PRIV
  109. {
  110. public:
  111. DIALOG_FILTER_SELECTION::OPTIONS m_filterOpts;
  112. };
  113. PCB_SELECTION_TOOL::PCB_SELECTION_TOOL() :
  114. SELECTION_TOOL( "common.InteractiveSelection" ),
  115. m_frame( nullptr ),
  116. m_isFootprintEditor( false ),
  117. m_nonModifiedCursor( KICURSOR::ARROW ),
  118. m_enteredGroup( nullptr ),
  119. m_priv( std::make_unique<PRIV>() )
  120. {
  121. m_filter.lockedItems = false;
  122. m_filter.footprints = true;
  123. m_filter.text = true;
  124. m_filter.tracks = true;
  125. m_filter.vias = true;
  126. m_filter.pads = true;
  127. m_filter.graphics = true;
  128. m_filter.zones = true;
  129. m_filter.keepouts = true;
  130. m_filter.dimensions = true;
  131. m_filter.otherItems = true;
  132. }
  133. PCB_SELECTION_TOOL::~PCB_SELECTION_TOOL()
  134. {
  135. getView()->Remove( &m_selection );
  136. getView()->Remove( &m_enteredGroupOverlay );
  137. Disconnect( wxEVT_TIMER, wxTimerEventHandler( PCB_SELECTION_TOOL::onDisambiguationExpire ), nullptr, this );
  138. }
  139. bool PCB_SELECTION_TOOL::Init()
  140. {
  141. PCB_BASE_FRAME* frame = getEditFrame<PCB_BASE_FRAME>();
  142. if( frame && frame->IsType( FRAME_FOOTPRINT_VIEWER ) )
  143. {
  144. frame->AddStandardSubMenus( *m_menu.get() );
  145. return true;
  146. }
  147. std::shared_ptr<SELECT_MENU> selectMenu = std::make_shared<SELECT_MENU>();
  148. selectMenu->SetTool( this );
  149. m_menu->RegisterSubMenu( selectMenu );
  150. static const std::vector<KICAD_T> tableCellTypes = { PCB_TABLECELL_T };
  151. auto& menu = m_menu->GetMenu();
  152. auto activeToolCondition =
  153. [ frame ] ( const SELECTION& aSel )
  154. {
  155. return !frame->ToolStackIsEmpty();
  156. };
  157. auto haveHighlight =
  158. [&]( const SELECTION& sel )
  159. {
  160. KIGFX::RENDER_SETTINGS* cfg = m_toolMgr->GetView()->GetPainter()->GetSettings();
  161. return !cfg->GetHighlightNetCodes().empty();
  162. };
  163. auto groupEnterCondition =
  164. SELECTION_CONDITIONS::Count( 1 ) && SELECTION_CONDITIONS::HasType( PCB_GROUP_T );
  165. auto inGroupCondition =
  166. [this] ( const SELECTION& )
  167. {
  168. return m_enteredGroup != nullptr;
  169. };
  170. auto tableCellSelection = SELECTION_CONDITIONS::MoreThan( 0 )
  171. && SELECTION_CONDITIONS::OnlyTypes( tableCellTypes );
  172. if( frame && frame->IsType( FRAME_PCB_EDITOR ) )
  173. {
  174. menu.AddMenu( selectMenu.get(), SELECTION_CONDITIONS::NotEmpty );
  175. menu.AddSeparator( 1000 );
  176. }
  177. // "Cancel" goes at the top of the context menu when a tool is active
  178. menu.AddItem( ACTIONS::cancelInteractive, activeToolCondition, 1 );
  179. menu.AddItem( ACTIONS::groupEnter, groupEnterCondition, 1 );
  180. menu.AddItem( ACTIONS::groupLeave, inGroupCondition, 1 );
  181. menu.AddItem( PCB_ACTIONS::placeLinkedDesignBlock, groupEnterCondition, 1 );
  182. menu.AddItem( PCB_ACTIONS::saveToLinkedDesignBlock, groupEnterCondition, 1 );
  183. menu.AddItem( PCB_ACTIONS::clearHighlight, haveHighlight, 1 );
  184. menu.AddSeparator( haveHighlight, 1 );
  185. menu.AddItem( ACTIONS::selectColumns, tableCellSelection, 2 );
  186. menu.AddItem( ACTIONS::selectRows, tableCellSelection, 2 );
  187. menu.AddItem( ACTIONS::selectTable, tableCellSelection, 2 );
  188. menu.AddSeparator( 1 );
  189. if( frame )
  190. frame->AddStandardSubMenus( *m_menu.get() );
  191. m_disambiguateTimer.SetOwner( this );
  192. Connect( m_disambiguateTimer.GetId(), wxEVT_TIMER,
  193. wxTimerEventHandler( PCB_SELECTION_TOOL::onDisambiguationExpire ), nullptr, this );
  194. return true;
  195. }
  196. void PCB_SELECTION_TOOL::Reset( RESET_REASON aReason )
  197. {
  198. m_frame = getEditFrame<PCB_BASE_FRAME>();
  199. m_isFootprintEditor = m_frame->IsType( FRAME_FOOTPRINT_EDITOR );
  200. if( aReason != TOOL_BASE::REDRAW )
  201. {
  202. if( m_enteredGroup )
  203. ExitGroup();
  204. // Deselect any item being currently in edit, to avoid unexpected behavior and remove
  205. // pointers to the selected items from containers.
  206. ClearSelection( true );
  207. }
  208. if( aReason == TOOL_BASE::MODEL_RELOAD )
  209. getView()->GetPainter()->GetSettings()->SetHighlight( false );
  210. // Reinsert the VIEW_GROUP, in case it was removed from the VIEW
  211. view()->Remove( &m_selection );
  212. view()->Add( &m_selection );
  213. view()->Remove( &m_enteredGroupOverlay );
  214. view()->Add( &m_enteredGroupOverlay );
  215. }
  216. void PCB_SELECTION_TOOL::OnIdle( wxIdleEvent& aEvent )
  217. {
  218. if( m_frame->ToolStackIsEmpty() && !m_multiple )
  219. {
  220. wxMouseState keyboardState = wxGetMouseState();
  221. setModifiersState( keyboardState.ShiftDown(), keyboardState.ControlDown(),
  222. keyboardState.AltDown() );
  223. if( m_additive )
  224. m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::ADD );
  225. else if( m_subtractive )
  226. m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::SUBTRACT );
  227. else if( m_exclusive_or )
  228. m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::XOR );
  229. else
  230. m_frame->GetCanvas()->SetCurrentCursor( m_nonModifiedCursor );
  231. }
  232. }
  233. int PCB_SELECTION_TOOL::Main( const TOOL_EVENT& aEvent )
  234. {
  235. // Main loop: keep receiving events
  236. while( TOOL_EVENT* evt = Wait() )
  237. {
  238. MOUSE_DRAG_ACTION dragAction = m_frame->GetDragAction();
  239. TRACK_DRAG_ACTION trackDragAction = TRACK_DRAG_ACTION::MOVE;
  240. if( PCBNEW_SETTINGS* cfg = m_frame->GetPcbNewSettings() )
  241. trackDragAction = cfg->m_TrackDragAction;
  242. // on left click, a selection is made, depending on modifiers ALT, SHIFT, CTRL:
  243. setModifiersState( evt->Modifier( MD_SHIFT ), evt->Modifier( MD_CTRL ),
  244. evt->Modifier( MD_ALT ) );
  245. PCB_BASE_FRAME* frame = getEditFrame<PCB_BASE_FRAME>();
  246. bool brd_editor = frame && frame->IsType( FRAME_PCB_EDITOR );
  247. ROUTER_TOOL* router = m_toolMgr->GetTool<ROUTER_TOOL>();
  248. // If the router tool is active, don't override
  249. if( router && router->IsToolActive() && router->RoutingInProgress() )
  250. {
  251. evt->SetPassEvent();
  252. }
  253. else if( evt->IsMouseDown( BUT_LEFT ) )
  254. {
  255. // Avoid triggering when running under other tools
  256. PCB_POINT_EDITOR *pt_tool = m_toolMgr->GetTool<PCB_POINT_EDITOR>();
  257. if( m_frame->ToolStackIsEmpty() && pt_tool && !pt_tool->HasPoint() )
  258. {
  259. m_originalCursor = m_toolMgr->GetMousePosition();
  260. m_disambiguateTimer.StartOnce( ADVANCED_CFG::GetCfg().m_DisambiguationMenuDelay );
  261. }
  262. }
  263. else if( evt->IsClick( BUT_LEFT ) )
  264. {
  265. // If there is no disambiguation, this routine is still running and will
  266. // register a `click` event when released
  267. if( m_disambiguateTimer.IsRunning() )
  268. {
  269. m_disambiguateTimer.Stop();
  270. // Single click? Select single object
  271. if( m_highlight_modifier && brd_editor )
  272. {
  273. m_toolMgr->RunAction( PCB_ACTIONS::highlightNet );
  274. }
  275. else
  276. {
  277. m_frame->ClearFocus();
  278. selectPoint( evt->Position() );
  279. }
  280. }
  281. m_canceledMenu = false;
  282. }
  283. else if( evt->IsClick( BUT_RIGHT ) )
  284. {
  285. m_disambiguateTimer.Stop();
  286. // Right click? if there is any object - show the context menu
  287. bool selectionCancelled = false;
  288. if( m_selection.Empty() )
  289. {
  290. selectPoint( evt->Position(), false, &selectionCancelled );
  291. m_selection.SetIsHover( true );
  292. }
  293. // Show selection before opening menu
  294. m_frame->GetCanvas()->ForceRefresh();
  295. if( !selectionCancelled )
  296. {
  297. m_toolMgr->VetoContextMenuMouseWarp();
  298. m_menu->ShowContextMenu( m_selection );
  299. }
  300. }
  301. else if( evt->IsDblClick( BUT_LEFT ) )
  302. {
  303. m_disambiguateTimer.Stop();
  304. // Double clicks make no sense in the footprint viewer
  305. if( frame && frame->IsType( FRAME_FOOTPRINT_VIEWER ) )
  306. {
  307. evt->SetPassEvent();
  308. continue;
  309. }
  310. // Double click? Display the properties window
  311. m_frame->ClearFocus();
  312. if( m_selection.Empty() )
  313. selectPoint( evt->Position() );
  314. if( m_selection.GetSize() == 1 && m_selection[0]->Type() == PCB_GROUP_T )
  315. EnterGroup();
  316. else
  317. m_toolMgr->RunAction( PCB_ACTIONS::properties );
  318. }
  319. else if( evt->IsDblClick( BUT_MIDDLE ) )
  320. {
  321. // Middle double click? Do zoom to fit or zoom to objects
  322. if( evt->Modifier( MD_CTRL ) ) // Is CTRL key down?
  323. m_toolMgr->RunAction( ACTIONS::zoomFitObjects );
  324. else
  325. m_toolMgr->RunAction( ACTIONS::zoomFitScreen );
  326. }
  327. else if( evt->Action() == TA_MOUSE_WHEEL )
  328. {
  329. int field = -1;
  330. if( evt->Modifier() == ( MD_SHIFT | MD_ALT ) )
  331. field = 0;
  332. else if( evt->Modifier() == ( MD_CTRL | MD_ALT ) )
  333. field = 1;
  334. // any more?
  335. if( field >= 0 )
  336. {
  337. const int delta = evt->Parameter<int>();
  338. ACTIONS::INCREMENT incParams{
  339. delta > 0 ? 1 : -1,
  340. field
  341. };
  342. m_toolMgr->RunAction( ACTIONS::increment, incParams );
  343. }
  344. }
  345. else if( evt->IsDrag( BUT_LEFT ) )
  346. {
  347. m_disambiguateTimer.Stop();
  348. // Is another tool already moving a new object? Don't allow a drag start
  349. if( !m_selection.Empty() && m_selection[0]->HasFlag( IS_NEW | IS_MOVING ) )
  350. {
  351. evt->SetPassEvent();
  352. continue;
  353. }
  354. // Drag with LMB? Select multiple objects (or at least draw a selection box)
  355. // or drag them
  356. m_frame->ClearFocus();
  357. m_toolMgr->ProcessEvent( EVENTS::InhibitSelectionEditing );
  358. GENERAL_COLLECTOR hoverCells;
  359. if( m_isFootprintEditor && board()->GetFirstFootprint() )
  360. {
  361. hoverCells.Collect( board()->GetFirstFootprint(), { PCB_TABLECELL_T }, evt->DragOrigin(),
  362. getCollectorsGuide() );
  363. }
  364. else
  365. {
  366. hoverCells.Collect( board(), { PCB_TABLECELL_T }, evt->DragOrigin(), getCollectorsGuide() );
  367. }
  368. if( hoverCells.GetCount() )
  369. {
  370. if( m_selection.Empty() || SELECTION_CONDITIONS::OnlyTypes( { PCB_TABLECELL_T } )( m_selection ) )
  371. {
  372. selectTableCells( static_cast<PCB_TABLE*>( hoverCells[0]->GetParent() ) );
  373. }
  374. else
  375. {
  376. m_toolMgr->RunAction( PCB_ACTIONS::move );
  377. }
  378. }
  379. else if( hasModifier() || dragAction == MOUSE_DRAG_ACTION::SELECT )
  380. {
  381. SelectRectArea( aEvent );
  382. }
  383. else if( m_selection.Empty() && dragAction != MOUSE_DRAG_ACTION::DRAG_ANY )
  384. {
  385. SelectRectArea( aEvent );
  386. }
  387. else
  388. {
  389. // Don't allow starting a drag from a zone filled area that isn't already selected
  390. auto zoneFilledAreaFilter =
  391. []( const VECTOR2I& aWhere, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* aTool )
  392. {
  393. int accuracy = aCollector.GetGuide()->Accuracy();
  394. std::set<EDA_ITEM*> remove;
  395. for( EDA_ITEM* item : aCollector )
  396. {
  397. if( item->Type() == PCB_ZONE_T )
  398. {
  399. ZONE* zone = static_cast<ZONE*>( item );
  400. if( !zone->HitTestForCorner( aWhere, accuracy * 2 )
  401. && !zone->HitTestForEdge( aWhere, accuracy ) )
  402. {
  403. remove.insert( zone );
  404. }
  405. }
  406. }
  407. for( EDA_ITEM* item : remove )
  408. aCollector.Remove( item );
  409. };
  410. // See if we can drag before falling back to SelectRectArea()
  411. bool doDrag = false;
  412. if( evt->HasPosition() )
  413. {
  414. if( m_selection.Empty()
  415. && selectPoint( evt->DragOrigin(), false, nullptr, zoneFilledAreaFilter ) )
  416. {
  417. m_selection.SetIsHover( true );
  418. doDrag = true;
  419. }
  420. // Check if dragging has started within any of selected items bounding box.
  421. else if( evt->HasPosition() && selectionContains( evt->DragOrigin() ) )
  422. {
  423. doDrag = true;
  424. }
  425. }
  426. if( doDrag )
  427. {
  428. size_t segs = m_selection.CountType( PCB_TRACE_T );
  429. size_t arcs = m_selection.CountType( PCB_ARC_T );
  430. size_t vias = m_selection.CountType( PCB_VIA_T );
  431. // Note: multi-track dragging is currently supported, but not multi-via
  432. bool routable = ( segs >= 1 || arcs >= 1 || vias == 1 )
  433. && ( segs + arcs + vias == m_selection.GetSize() );
  434. if( routable && trackDragAction == TRACK_DRAG_ACTION::DRAG )
  435. m_toolMgr->RunAction( PCB_ACTIONS::drag45Degree );
  436. else if( routable && trackDragAction == TRACK_DRAG_ACTION::DRAG_FREE_ANGLE )
  437. m_toolMgr->RunAction( PCB_ACTIONS::dragFreeAngle );
  438. else
  439. m_toolMgr->RunAction( PCB_ACTIONS::move );
  440. }
  441. else
  442. {
  443. // Otherwise drag a selection box
  444. SelectRectArea( aEvent );
  445. }
  446. }
  447. }
  448. else if( evt->IsCancel() )
  449. {
  450. m_disambiguateTimer.Stop();
  451. m_frame->ClearFocus();
  452. if( !GetSelection().Empty() )
  453. {
  454. ClearSelection();
  455. }
  456. else if( evt->FirstResponder() == this && evt->GetCommandId() == (int) WXK_ESCAPE )
  457. {
  458. if( m_enteredGroup )
  459. {
  460. ExitGroup();
  461. }
  462. else
  463. {
  464. BOARD_INSPECTION_TOOL* controller = m_toolMgr->GetTool<BOARD_INSPECTION_TOOL>();
  465. try
  466. {
  467. if( controller && m_frame->GetPcbNewSettings()->m_ESCClearsNetHighlight )
  468. controller->ClearHighlight( *evt );
  469. }
  470. catch( const std::runtime_error& e )
  471. {
  472. wxCHECK_MSG( false, 0, e.what() );
  473. }
  474. }
  475. }
  476. }
  477. else
  478. {
  479. evt->SetPassEvent();
  480. }
  481. if( m_frame->ToolStackIsEmpty() )
  482. {
  483. // move cursor prediction
  484. if( !hasModifier()
  485. && dragAction == MOUSE_DRAG_ACTION::DRAG_SELECTED
  486. && !m_selection.Empty()
  487. && evt->HasPosition()
  488. && selectionContains( evt->Position() ) )
  489. {
  490. m_nonModifiedCursor = KICURSOR::MOVING;
  491. }
  492. else
  493. {
  494. m_nonModifiedCursor = KICURSOR::ARROW;
  495. }
  496. }
  497. }
  498. // Shutting down; clear the selection
  499. m_selection.Clear();
  500. m_disambiguateTimer.Stop();
  501. return 0;
  502. }
  503. void PCB_SELECTION_TOOL::EnterGroup()
  504. {
  505. wxCHECK_RET( m_selection.GetSize() == 1 && m_selection[0]->Type() == PCB_GROUP_T,
  506. wxT( "EnterGroup called when selection is not a single group" ) );
  507. PCB_GROUP* aGroup = static_cast<PCB_GROUP*>( m_selection[0] );
  508. if( m_enteredGroup != nullptr )
  509. ExitGroup();
  510. ClearSelection();
  511. m_enteredGroup = aGroup;
  512. m_enteredGroup->SetFlags( ENTERED );
  513. for( EDA_ITEM* member : m_enteredGroup->GetItems() )
  514. select( member );
  515. m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
  516. view()->Hide( m_enteredGroup, true );
  517. m_enteredGroupOverlay.Add( m_enteredGroup );
  518. view()->Update( &m_enteredGroupOverlay );
  519. }
  520. void PCB_SELECTION_TOOL::ExitGroup( bool aSelectGroup )
  521. {
  522. // Only continue if there is a group entered
  523. if( m_enteredGroup == nullptr )
  524. return;
  525. m_enteredGroup->ClearFlags( ENTERED );
  526. view()->Hide( m_enteredGroup, false );
  527. ClearSelection();
  528. if( aSelectGroup )
  529. {
  530. select( m_enteredGroup );
  531. m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
  532. }
  533. m_enteredGroupOverlay.Clear();
  534. m_enteredGroup = nullptr;
  535. view()->Update( &m_enteredGroupOverlay );
  536. }
  537. PCB_SELECTION& PCB_SELECTION_TOOL::GetSelection()
  538. {
  539. return m_selection;
  540. }
  541. PCB_SELECTION& PCB_SELECTION_TOOL::RequestSelection( CLIENT_SELECTION_FILTER aClientFilter,
  542. bool aConfirmLockedItems )
  543. {
  544. bool selectionEmpty = m_selection.Empty();
  545. m_selection.SetIsHover( selectionEmpty );
  546. if( selectionEmpty )
  547. {
  548. m_toolMgr->RunAction( ACTIONS::selectionCursor, aClientFilter );
  549. m_selection.ClearReferencePoint();
  550. }
  551. if( aClientFilter )
  552. {
  553. enum DISPOSITION { BEFORE = 1, AFTER, BOTH };
  554. std::map<EDA_ITEM*, DISPOSITION> itemDispositions;
  555. GENERAL_COLLECTORS_GUIDE guide = getCollectorsGuide();
  556. GENERAL_COLLECTOR collector;
  557. collector.SetGuide( &guide );
  558. for( EDA_ITEM* item : m_selection )
  559. {
  560. collector.Append( item );
  561. itemDispositions[ item ] = BEFORE;
  562. }
  563. aClientFilter( VECTOR2I(), collector, this );
  564. for( EDA_ITEM* item : collector )
  565. {
  566. if( itemDispositions.count( item ) )
  567. itemDispositions[ item ] = BOTH;
  568. else
  569. itemDispositions[ item ] = AFTER;
  570. }
  571. // Unhighlight the BEFORE items before highlighting the AFTER items.
  572. // This is so that in the case of groups, if aClientFilter replaces a selection
  573. // with the enclosing group, the unhighlight of the element doesn't undo the
  574. // recursive highlighting of that element by the group.
  575. for( std::pair<EDA_ITEM* const, DISPOSITION> itemDisposition : itemDispositions )
  576. {
  577. EDA_ITEM* item = itemDisposition.first;
  578. DISPOSITION disposition = itemDisposition.second;
  579. if( disposition == BEFORE )
  580. unhighlight( item, SELECTED, &m_selection );
  581. }
  582. for( std::pair<EDA_ITEM* const, DISPOSITION> itemDisposition : itemDispositions )
  583. {
  584. EDA_ITEM* item = itemDisposition.first;
  585. DISPOSITION disposition = itemDisposition.second;
  586. // Note that we must re-highlight even previously-highlighted items
  587. // (ie: disposition BOTH) in case we removed any of their children.
  588. if( disposition == AFTER || disposition == BOTH )
  589. highlight( item, SELECTED, &m_selection );
  590. }
  591. m_frame->GetCanvas()->ForceRefresh();
  592. }
  593. if( aConfirmLockedItems )
  594. {
  595. std::vector<BOARD_ITEM*> lockedItems;
  596. for( EDA_ITEM* item : m_selection )
  597. {
  598. if( !item->IsBOARD_ITEM() )
  599. continue;
  600. BOARD_ITEM* boardItem = static_cast<BOARD_ITEM*>( item );
  601. bool lockedDescendant = false;
  602. boardItem->RunOnChildren(
  603. [&]( BOARD_ITEM* curr_item )
  604. {
  605. if( curr_item->IsLocked() )
  606. lockedDescendant = true;
  607. },
  608. RECURSE_MODE::RECURSE );
  609. if( boardItem->IsLocked() || lockedDescendant )
  610. lockedItems.push_back( boardItem );
  611. }
  612. PCBNEW_SETTINGS* settings = m_frame->GetPcbNewSettings();
  613. if( !lockedItems.empty() )
  614. {
  615. DIALOG_LOCKED_ITEMS_QUERY dlg( frame(), lockedItems.size(), settings->m_LockingOptions );
  616. switch( dlg.ShowModal() )
  617. {
  618. case wxID_OK:
  619. // remove locked items from selection
  620. for( BOARD_ITEM* item : lockedItems )
  621. unselect( item );
  622. break;
  623. case wxID_CANCEL:
  624. // cancel operation
  625. ClearSelection();
  626. break;
  627. case wxID_APPLY:
  628. // continue with operation with current selection
  629. break;
  630. }
  631. }
  632. }
  633. return m_selection;
  634. }
  635. const GENERAL_COLLECTORS_GUIDE PCB_SELECTION_TOOL::getCollectorsGuide() const
  636. {
  637. GENERAL_COLLECTORS_GUIDE guide( board()->GetVisibleLayers(),
  638. (PCB_LAYER_ID) view()->GetTopLayer(), view() );
  639. bool padsDisabled = !board()->IsElementVisible( LAYER_PADS );
  640. // account for the globals
  641. guide.SetIgnoreFPTextOnBack( !board()->IsElementVisible( LAYER_FP_TEXT ) );
  642. guide.SetIgnoreFPTextOnFront( !board()->IsElementVisible( LAYER_FP_TEXT ) );
  643. guide.SetIgnoreFootprintsOnBack( !board()->IsElementVisible( LAYER_FOOTPRINTS_BK ) );
  644. guide.SetIgnoreFootprintsOnFront( !board()->IsElementVisible( LAYER_FOOTPRINTS_FR ) );
  645. guide.SetIgnorePadsOnBack( padsDisabled );
  646. guide.SetIgnorePadsOnFront( padsDisabled );
  647. guide.SetIgnoreThroughHolePads( padsDisabled );
  648. guide.SetIgnoreFPValues( !board()->IsElementVisible( LAYER_FP_VALUES ) );
  649. guide.SetIgnoreFPReferences( !board()->IsElementVisible( LAYER_FP_REFERENCES ) );
  650. guide.SetIgnoreThroughVias( ! board()->IsElementVisible( LAYER_VIAS ) );
  651. guide.SetIgnoreBlindBuriedVias( ! board()->IsElementVisible( LAYER_VIAS ) );
  652. guide.SetIgnoreMicroVias( ! board()->IsElementVisible( LAYER_VIAS ) );
  653. guide.SetIgnoreTracks( ! board()->IsElementVisible( LAYER_TRACKS ) );
  654. return guide;
  655. }
  656. bool PCB_SELECTION_TOOL::ctrlClickHighlights()
  657. {
  658. return m_frame && m_frame->GetPcbNewSettings()->m_CtrlClickHighlight && !m_isFootprintEditor;
  659. }
  660. bool PCB_SELECTION_TOOL::selectPoint( const VECTOR2I& aWhere, bool aOnDrag,
  661. bool* aSelectionCancelledFlag,
  662. CLIENT_SELECTION_FILTER aClientFilter )
  663. {
  664. GENERAL_COLLECTORS_GUIDE guide = getCollectorsGuide();
  665. GENERAL_COLLECTOR collector;
  666. const PCB_DISPLAY_OPTIONS& displayOpts = m_frame->GetDisplayOptions();
  667. guide.SetIgnoreZoneFills( displayOpts.m_ZoneDisplayMode != ZONE_DISPLAY_MODE::SHOW_FILLED );
  668. if( m_enteredGroup && !m_enteredGroup->GetBoundingBox().Contains( aWhere ) )
  669. ExitGroup();
  670. collector.Collect( board(), m_isFootprintEditor ? GENERAL_COLLECTOR::FootprintItems
  671. : GENERAL_COLLECTOR::AllBoardItems,
  672. aWhere, guide );
  673. // Remove unselectable items
  674. for( int i = collector.GetCount() - 1; i >= 0; --i )
  675. {
  676. if( !Selectable( collector[ i ] ) || ( aOnDrag && collector[i]->IsLocked() ) )
  677. collector.Remove( i );
  678. }
  679. m_selection.ClearReferencePoint();
  680. // Apply the stateful filter (remove items disabled by the Selection Filter)
  681. FilterCollectedItems( collector, false );
  682. // Allow the client to do tool- or action-specific filtering to see if we can get down
  683. // to a single item
  684. if( aClientFilter )
  685. aClientFilter( aWhere, collector, this );
  686. FilterCollectorForHierarchy( collector, false );
  687. FilterCollectorForFootprints( collector, aWhere );
  688. // For subtracting, we only want items that are selected
  689. if( m_subtractive )
  690. {
  691. for( int i = collector.GetCount() - 1; i >= 0; --i )
  692. {
  693. if( !collector[i]->IsSelected() )
  694. collector.Remove( i );
  695. }
  696. }
  697. // Apply some ugly heuristics to avoid disambiguation menus whenever possible
  698. if( collector.GetCount() > 1 && !m_skip_heuristics )
  699. {
  700. try
  701. {
  702. GuessSelectionCandidates( collector, aWhere );
  703. }
  704. catch( const std::exception& exc )
  705. {
  706. wxLogWarning( wxS( "Exception \"%s\" occurred attempting to guess selection "
  707. "candidates." ), exc.what() );
  708. return false;
  709. }
  710. }
  711. // If still more than one item we're going to have to ask the user.
  712. if( collector.GetCount() > 1 )
  713. {
  714. if( aOnDrag )
  715. Wait( TOOL_EVENT( TC_ANY, TA_MOUSE_UP, BUT_LEFT ) );
  716. if( !doSelectionMenu( &collector ) )
  717. {
  718. if( aSelectionCancelledFlag )
  719. *aSelectionCancelledFlag = true;
  720. return false;
  721. }
  722. }
  723. int addedCount = 0;
  724. bool anySubtracted = false;
  725. if( !m_additive && !m_subtractive && !m_exclusive_or )
  726. {
  727. if( m_selection.GetSize() > 0 )
  728. {
  729. ClearSelection( true /*quiet mode*/ );
  730. anySubtracted = true;
  731. }
  732. }
  733. if( collector.GetCount() > 0 )
  734. {
  735. for( int i = 0; i < collector.GetCount(); ++i )
  736. {
  737. if( m_subtractive || ( m_exclusive_or && collector[i]->IsSelected() ) )
  738. {
  739. unselect( collector[i] );
  740. anySubtracted = true;
  741. }
  742. else
  743. {
  744. select( collector[i] );
  745. addedCount++;
  746. }
  747. }
  748. }
  749. if( addedCount == 1 )
  750. {
  751. m_toolMgr->ProcessEvent( EVENTS::PointSelectedEvent );
  752. return true;
  753. }
  754. else if( addedCount > 1 )
  755. {
  756. m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
  757. return true;
  758. }
  759. else if( anySubtracted )
  760. {
  761. m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent );
  762. return true;
  763. }
  764. return false;
  765. }
  766. bool PCB_SELECTION_TOOL::selectCursor( bool aForceSelect, CLIENT_SELECTION_FILTER aClientFilter )
  767. {
  768. if( aForceSelect || m_selection.Empty() )
  769. {
  770. ClearSelection( true /*quiet mode*/ );
  771. selectPoint( getViewControls()->GetCursorPosition( false ), false, nullptr, aClientFilter );
  772. }
  773. return !m_selection.Empty();
  774. }
  775. // Some navigation actions are allowed in selectMultiple
  776. const TOOL_ACTION* allowedActions[] = { &ACTIONS::panUp, &ACTIONS::panDown,
  777. &ACTIONS::panLeft, &ACTIONS::panRight,
  778. &ACTIONS::cursorUp, &ACTIONS::cursorDown,
  779. &ACTIONS::cursorLeft, &ACTIONS::cursorRight,
  780. &ACTIONS::cursorUpFast, &ACTIONS::cursorDownFast,
  781. &ACTIONS::cursorLeftFast, &ACTIONS::cursorRightFast,
  782. &ACTIONS::zoomIn, &ACTIONS::zoomOut,
  783. &ACTIONS::zoomInCenter, &ACTIONS::zoomOutCenter,
  784. &ACTIONS::zoomCenter, &ACTIONS::zoomFitScreen,
  785. &ACTIONS::zoomFitObjects, nullptr };
  786. static void passEvent( TOOL_EVENT* const aEvent, const TOOL_ACTION* const aAllowedActions[] )
  787. {
  788. for( int i = 0; aAllowedActions[i]; ++i )
  789. {
  790. if( aEvent->IsAction( aAllowedActions[i] ) )
  791. {
  792. aEvent->SetPassEvent();
  793. break;
  794. }
  795. }
  796. }
  797. bool PCB_SELECTION_TOOL::selectTableCells( PCB_TABLE* aTable )
  798. {
  799. bool cancelled = false; // Was the tool canceled while it was running?
  800. m_multiple = true; // Multiple selection mode is active
  801. for( PCB_TABLECELL* cell : aTable->GetCells() )
  802. {
  803. if( cell->IsSelected() )
  804. cell->SetFlags( CANDIDATE );
  805. else
  806. cell->ClearFlags( CANDIDATE );
  807. }
  808. auto wasSelected =
  809. []( EDA_ITEM* aItem )
  810. {
  811. return ( aItem->GetFlags() & CANDIDATE ) > 0;
  812. };
  813. while( TOOL_EVENT* evt = Wait() )
  814. {
  815. if( evt->IsCancelInteractive() || evt->IsActivate() )
  816. {
  817. cancelled = true;
  818. break;
  819. }
  820. else if( evt->IsDrag( BUT_LEFT ) )
  821. {
  822. getViewControls()->SetAutoPan( true );
  823. BOX2I selectionRect( evt->DragOrigin(), evt->Position() - evt->DragOrigin() );
  824. selectionRect.Normalize();
  825. for( PCB_TABLECELL* cell : aTable->GetCells() )
  826. {
  827. bool doSelect = false;
  828. if( cell->HitTest( selectionRect, false ) )
  829. {
  830. if( m_subtractive )
  831. doSelect = false;
  832. else if( m_exclusive_or )
  833. doSelect = !wasSelected( cell );
  834. else
  835. doSelect = true;
  836. }
  837. else if( wasSelected( cell ) )
  838. {
  839. doSelect = m_additive || m_subtractive || m_exclusive_or;
  840. }
  841. if( doSelect && !cell->IsSelected() )
  842. select( cell );
  843. else if( !doSelect && cell->IsSelected() )
  844. unselect( cell );
  845. }
  846. }
  847. else if( evt->IsMouseUp( BUT_LEFT ) )
  848. {
  849. m_selection.SetIsHover( false );
  850. bool anyAdded = false;
  851. bool anySubtracted = false;
  852. for( PCB_TABLECELL* cell : aTable->GetCells() )
  853. {
  854. if( cell->IsSelected() && !wasSelected( cell ) )
  855. anyAdded = true;
  856. else if( wasSelected( cell ) && !cell->IsSelected() )
  857. anySubtracted = true;
  858. }
  859. // Inform other potentially interested tools
  860. if( anyAdded )
  861. m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
  862. if( anySubtracted )
  863. m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent );
  864. break; // Stop waiting for events
  865. }
  866. else
  867. {
  868. // Allow some actions for navigation
  869. passEvent( evt, allowedActions );
  870. }
  871. }
  872. getViewControls()->SetAutoPan( false );
  873. m_multiple = false; // Multiple selection mode is inactive
  874. if( !cancelled )
  875. m_selection.ClearReferencePoint();
  876. return cancelled;
  877. }
  878. int PCB_SELECTION_TOOL::SelectRectArea( const TOOL_EVENT& aEvent )
  879. {
  880. bool cancelled = false; // Was the tool canceled while it was running?
  881. m_multiple = true; // Multiple selection mode is active
  882. KIGFX::VIEW* view = getView();
  883. bool fixedMode = false;
  884. SELECTION_MODE selectionMode = SELECTION_MODE::INSIDE_RECTANGLE;
  885. if( aEvent.HasParameter() )
  886. {
  887. fixedMode = true;
  888. selectionMode = aEvent.Parameter<SELECTION_MODE>();
  889. }
  890. KIGFX::PREVIEW::SELECTION_AREA area;
  891. view->Add( &area );
  892. while( TOOL_EVENT* evt = Wait() )
  893. {
  894. if( !fixedMode )
  895. {
  896. int width = area.GetEnd().x - area.GetOrigin().x;
  897. /* Selection mode depends on direction of drag-selection:
  898. * Left > Right : Select objects that are fully enclosed by selection
  899. * Right > Left : Select objects that are crossed by selection
  900. */
  901. bool touchingSelection = width >= 0 ? false : true;
  902. if( view->IsMirroredX() )
  903. touchingSelection = !touchingSelection;
  904. selectionMode = touchingSelection ? SELECTION_MODE::TOUCHING_RECTANGLE
  905. : SELECTION_MODE::INSIDE_RECTANGLE;
  906. }
  907. if( selectionMode == SELECTION_MODE::INSIDE_RECTANGLE )
  908. m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::SELECT_WINDOW );
  909. else
  910. m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::SELECT_LASSO );
  911. if( evt->IsCancelInteractive() || evt->IsActivate() )
  912. {
  913. cancelled = true;
  914. break;
  915. }
  916. if( evt->IsDrag( BUT_LEFT ) )
  917. {
  918. if( !m_drag_additive && !m_drag_subtractive )
  919. {
  920. if( m_selection.GetSize() > 0 )
  921. {
  922. ClearSelection( true /*quiet mode*/ );
  923. m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent );
  924. }
  925. }
  926. // Start drawing a selection box
  927. area.SetOrigin( evt->DragOrigin() );
  928. area.SetEnd( evt->Position() );
  929. area.SetAdditive( m_drag_additive );
  930. area.SetSubtractive( m_drag_subtractive );
  931. area.SetExclusiveOr( false );
  932. area.SetMode( selectionMode );
  933. view->SetVisible( &area, true );
  934. view->Update( &area );
  935. getViewControls()->SetAutoPan( true );
  936. }
  937. if( evt->IsMouseUp( BUT_LEFT ) )
  938. {
  939. getViewControls()->SetAutoPan( false );
  940. // End drawing the selection box
  941. view->SetVisible( &area, false );
  942. SelectMultiple( area, m_subtractive, m_exclusive_or );
  943. break; // Stop waiting for events
  944. }
  945. // Allow some actions for navigation
  946. passEvent( evt, allowedActions );
  947. }
  948. getViewControls()->SetAutoPan( false );
  949. // Stop drawing the selection box
  950. view->Remove( &area );
  951. m_multiple = false; // Multiple selection mode is inactive
  952. if( !cancelled )
  953. m_selection.ClearReferencePoint();
  954. m_toolMgr->ProcessEvent( EVENTS::UninhibitSelectionEditing );
  955. return cancelled;
  956. }
  957. void PCB_SELECTION_TOOL::SelectMultiple( KIGFX::PREVIEW::SELECTION_AREA& aArea, bool aSubtractive,
  958. bool aExclusiveOr )
  959. {
  960. KIGFX::VIEW* view = getView();
  961. bool anyAdded = false;
  962. bool anySubtracted = false;
  963. SELECTION_MODE selectionMode = aArea.GetMode();
  964. bool containedMode = ( selectionMode == SELECTION_MODE::INSIDE_RECTANGLE
  965. || selectionMode == SELECTION_MODE::INSIDE_LASSO ) ? true : false;
  966. bool boxMode = ( selectionMode == SELECTION_MODE::INSIDE_RECTANGLE
  967. || selectionMode == SELECTION_MODE::TOUCHING_RECTANGLE ) ? true : false;
  968. std::vector<KIGFX::VIEW::LAYER_ITEM_PAIR> candidates;
  969. BOX2I selectionBox = aArea.ViewBBox();
  970. view->Query( selectionBox, candidates ); // Get the list of nearby items
  971. GENERAL_COLLECTOR collector;
  972. GENERAL_COLLECTOR padsCollector;
  973. std::set<EDA_ITEM*> group_items;
  974. for( PCB_GROUP* group : board()->Groups() )
  975. {
  976. // The currently entered group does not get limited
  977. if( m_enteredGroup == group )
  978. continue;
  979. std::unordered_set<EDA_ITEM*>& newset = group->GetItems();
  980. auto boxContained =
  981. [&]( const BOX2I& aBox )
  982. {
  983. return boxMode ? selectionBox.Contains( aBox )
  984. : KIGEOM::BoxHitTest( aArea.GetPoly(), aBox, true );
  985. };
  986. // If we are not greedy and have selected the whole group, add just one item
  987. // to allow it to be promoted to the group later
  988. if( containedMode && boxContained( group->GetBoundingBox() ) && newset.size() )
  989. {
  990. for( EDA_ITEM* group_item : newset )
  991. {
  992. if( !group_item->IsBOARD_ITEM() )
  993. continue;
  994. if( Selectable( static_cast<BOARD_ITEM*>( group_item ) ) )
  995. collector.Append( *newset.begin() );
  996. }
  997. }
  998. for( EDA_ITEM* group_item : newset )
  999. group_items.emplace( group_item );
  1000. }
  1001. auto hitTest =
  1002. [&]( const EDA_ITEM* aItem )
  1003. {
  1004. return boxMode ? aItem->HitTest( selectionBox, containedMode )
  1005. : aItem->HitTest( aArea.GetPoly(), containedMode );
  1006. };
  1007. for( const auto& [item, layer] : candidates )
  1008. {
  1009. if( !item->IsBOARD_ITEM() )
  1010. continue;
  1011. BOARD_ITEM* boardItem = static_cast<BOARD_ITEM*>( item );
  1012. if( Selectable( boardItem ) && hitTest( boardItem )
  1013. && ( !containedMode || !group_items.count( boardItem ) ) )
  1014. {
  1015. if( boardItem->Type() == PCB_PAD_T && !m_isFootprintEditor )
  1016. padsCollector.Append( boardItem );
  1017. else
  1018. collector.Append( boardItem );
  1019. }
  1020. }
  1021. // Apply the stateful filter
  1022. FilterCollectedItems( collector, true );
  1023. FilterCollectorForHierarchy( collector, true );
  1024. // If we selected nothing but pads, allow them to be selected
  1025. if( collector.GetCount() == 0 )
  1026. {
  1027. collector = padsCollector;
  1028. FilterCollectedItems( collector, true );
  1029. FilterCollectorForHierarchy( collector, true );
  1030. }
  1031. // Sort the filtered selection by rows and columns to have a nice default
  1032. // for tools that can use it.
  1033. std::sort( collector.begin(), collector.end(),
  1034. []( EDA_ITEM* a, EDA_ITEM* b )
  1035. {
  1036. VECTOR2I aPos = a->GetPosition();
  1037. VECTOR2I bPos = b->GetPosition();
  1038. if( aPos.y == bPos.y )
  1039. return aPos.x < bPos.x;
  1040. return aPos.y < bPos.y;
  1041. } );
  1042. for( EDA_ITEM* i : collector )
  1043. {
  1044. if( !i->IsBOARD_ITEM() )
  1045. continue;
  1046. BOARD_ITEM* item = static_cast<BOARD_ITEM*>( i );
  1047. if( aSubtractive || ( aExclusiveOr && item->IsSelected() ) )
  1048. {
  1049. unselect( item );
  1050. anySubtracted = true;
  1051. }
  1052. else
  1053. {
  1054. select( item );
  1055. anyAdded = true;
  1056. }
  1057. }
  1058. m_selection.SetIsHover( false );
  1059. // Inform other potentially interested tools
  1060. if( anyAdded )
  1061. m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
  1062. else if( anySubtracted )
  1063. m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent );
  1064. }
  1065. int PCB_SELECTION_TOOL::disambiguateCursor( const TOOL_EVENT& aEvent )
  1066. {
  1067. wxMouseState keyboardState = wxGetMouseState();
  1068. setModifiersState( keyboardState.ShiftDown(), keyboardState.ControlDown(),
  1069. keyboardState.AltDown() );
  1070. m_skip_heuristics = true;
  1071. selectPoint( m_originalCursor, false, &m_canceledMenu );
  1072. m_skip_heuristics = false;
  1073. return 0;
  1074. }
  1075. int PCB_SELECTION_TOOL::CursorSelection( const TOOL_EVENT& aEvent )
  1076. {
  1077. CLIENT_SELECTION_FILTER aClientFilter = aEvent.Parameter<CLIENT_SELECTION_FILTER>();
  1078. selectCursor( false, aClientFilter );
  1079. return 0;
  1080. }
  1081. int PCB_SELECTION_TOOL::ClearSelection( const TOOL_EVENT& aEvent )
  1082. {
  1083. ClearSelection();
  1084. return 0;
  1085. }
  1086. int PCB_SELECTION_TOOL::SelectAll( const TOOL_EVENT& aEvent )
  1087. {
  1088. GENERAL_COLLECTOR collection;
  1089. BOX2I selectionBox;
  1090. selectionBox.SetMaximum();
  1091. getView()->Query( selectionBox,
  1092. [&]( KIGFX::VIEW_ITEM* viewItem ) -> bool
  1093. {
  1094. if( viewItem->IsBOARD_ITEM() )
  1095. {
  1096. BOARD_ITEM* item = static_cast<BOARD_ITEM*>( viewItem );
  1097. if( item && Selectable( item ) && itemPassesFilter( item, true ) )
  1098. collection.Append( item );
  1099. }
  1100. return true;
  1101. } );
  1102. FilterCollectorForHierarchy( collection, true );
  1103. for( EDA_ITEM* item : collection )
  1104. select( item );
  1105. m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
  1106. m_frame->GetCanvas()->ForceRefresh();
  1107. return 0;
  1108. }
  1109. int PCB_SELECTION_TOOL::UnselectAll( const TOOL_EVENT& aEvent )
  1110. {
  1111. BOX2I selectionBox;
  1112. selectionBox.SetMaximum();
  1113. getView()->Query( selectionBox,
  1114. [&]( KIGFX::VIEW_ITEM* viewItem ) -> bool
  1115. {
  1116. if( viewItem->IsBOARD_ITEM() )
  1117. {
  1118. BOARD_ITEM* item = static_cast<BOARD_ITEM*>( viewItem );
  1119. if( item && Selectable( item ) )
  1120. unselect( item );
  1121. }
  1122. return true;
  1123. } );
  1124. m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent );
  1125. m_frame->GetCanvas()->ForceRefresh();
  1126. return 0;
  1127. }
  1128. void connectedItemFilter( const VECTOR2I&, GENERAL_COLLECTOR& aCollector,
  1129. PCB_SELECTION_TOOL* sTool )
  1130. {
  1131. // Narrow the collection down to a single BOARD_CONNECTED_ITEM for each represented net.
  1132. // All other items types are removed.
  1133. std::set<int> representedNets;
  1134. for( int i = aCollector.GetCount() - 1; i >= 0; i-- )
  1135. {
  1136. BOARD_CONNECTED_ITEM* item = dynamic_cast<BOARD_CONNECTED_ITEM*>( aCollector[i] );
  1137. if( !item )
  1138. aCollector.Remove( i );
  1139. else if ( representedNets.count( item->GetNetCode() ) )
  1140. aCollector.Remove( i );
  1141. else
  1142. representedNets.insert( item->GetNetCode() );
  1143. }
  1144. }
  1145. int PCB_SELECTION_TOOL::unrouteSelected( const TOOL_EVENT& aEvent )
  1146. {
  1147. std::deque<EDA_ITEM*> selectedItems = m_selection.GetItems();
  1148. // Get all footprints and pads
  1149. std::vector<BOARD_CONNECTED_ITEM*> toUnroute;
  1150. for( EDA_ITEM* item : selectedItems )
  1151. {
  1152. if( item->Type() == PCB_FOOTPRINT_T )
  1153. {
  1154. for( PAD* pad : static_cast<FOOTPRINT*>( item )->Pads() )
  1155. toUnroute.push_back( pad );
  1156. }
  1157. else if( BOARD_CONNECTED_ITEM::ClassOf( item ) )
  1158. {
  1159. toUnroute.push_back( static_cast<BOARD_CONNECTED_ITEM*>( item ) );
  1160. }
  1161. }
  1162. // Clear selection so we don't delete our footprints/pads
  1163. ClearSelection( true );
  1164. // Get the tracks on our list of pads, then delete them
  1165. selectAllConnectedTracks( toUnroute, STOP_CONDITION::STOP_AT_PAD );
  1166. m_toolMgr->RunAction( ACTIONS::doDelete );
  1167. // Reselect our footprint/pads as they were in our original selection
  1168. for( EDA_ITEM* item : selectedItems )
  1169. {
  1170. if( item->Type() == PCB_FOOTPRINT_T || item->Type() == PCB_PAD_T )
  1171. select( item );
  1172. }
  1173. return 0;
  1174. }
  1175. int PCB_SELECTION_TOOL::unrouteSegment( const TOOL_EVENT& aEvent )
  1176. {
  1177. std::deque<EDA_ITEM*> selectedItems = m_selection.GetItems();
  1178. // Get all footprints and pads
  1179. std::vector<BOARD_CONNECTED_ITEM*> toUnroute;
  1180. for( EDA_ITEM* item : selectedItems )
  1181. {
  1182. if( item->Type() == PCB_TRACE_T || item->Type() == PCB_ARC_T || item->Type() == PCB_VIA_T )
  1183. {
  1184. toUnroute.push_back( static_cast<BOARD_CONNECTED_ITEM*>( item ) );
  1185. }
  1186. }
  1187. // Get the tracks connecting to our starting objects
  1188. ClearSelection();
  1189. selectAllConnectedTracks( toUnroute, STOP_CONDITION::STOP_AT_SEGMENT );
  1190. std::deque<EDA_ITEM*> toSelectAfter;
  1191. // This will select the unroute items too, so filter them out
  1192. for( EDA_ITEM* item : m_selection.GetItemsSortedByTypeAndXY() )
  1193. {
  1194. if( !item->IsBOARD_ITEM() )
  1195. continue;
  1196. if( std::find( toUnroute.begin(), toUnroute.end(), item ) == toUnroute.end() )
  1197. toSelectAfter.push_back( item );
  1198. }
  1199. ClearSelection( true );
  1200. for( EDA_ITEM* item : toUnroute )
  1201. select( item );
  1202. m_toolMgr->RunAction( ACTIONS::doDelete );
  1203. // Now our after tracks so the user can continue backing up as desired
  1204. ClearSelection( true );
  1205. for( EDA_ITEM* item : toSelectAfter )
  1206. select( item );
  1207. return 0;
  1208. }
  1209. int PCB_SELECTION_TOOL::expandConnection( const TOOL_EVENT& aEvent )
  1210. {
  1211. // expandConnection will get called no matter whether the user selected a connected item or a
  1212. // non-connected shape (graphic on a non-copper layer). The algorithm for expanding to connected
  1213. // items is different from graphics, so they need to be handled separately.
  1214. unsigned initialCount = 0;
  1215. for( const EDA_ITEM* item : m_selection.GetItems() )
  1216. {
  1217. if( item->Type() == PCB_FOOTPRINT_T
  1218. || item->Type() == PCB_GENERATOR_T
  1219. || ( static_cast<const BOARD_ITEM*>( item )->IsConnected() ) )
  1220. {
  1221. initialCount++;
  1222. }
  1223. }
  1224. if( initialCount == 0 )
  1225. {
  1226. // First, process any graphic shapes we have
  1227. std::vector<PCB_SHAPE*> startShapes;
  1228. for( EDA_ITEM* item : m_selection.GetItems() )
  1229. {
  1230. if( isExpandableGraphicShape( item ) )
  1231. startShapes.push_back( static_cast<PCB_SHAPE*>( item ) );
  1232. }
  1233. // If no non-copper shapes; fall back to looking for connected items
  1234. if( !startShapes.empty() )
  1235. selectAllConnectedShapes( startShapes );
  1236. else
  1237. selectCursor( true, connectedItemFilter );
  1238. }
  1239. m_frame->SetStatusText( _( "Select/Expand Connection..." ) );
  1240. for( STOP_CONDITION stopCondition : { STOP_AT_JUNCTION, STOP_AT_PAD, STOP_NEVER } )
  1241. {
  1242. std::deque<EDA_ITEM*> selectedItems = m_selection.GetItems();
  1243. for( EDA_ITEM* item : selectedItems )
  1244. item->ClearTempFlags();
  1245. std::vector<BOARD_CONNECTED_ITEM*> startItems;
  1246. for( EDA_ITEM* item : selectedItems )
  1247. {
  1248. if( item->Type() == PCB_FOOTPRINT_T )
  1249. {
  1250. FOOTPRINT* footprint = static_cast<FOOTPRINT*>( item );
  1251. for( PAD* pad : footprint->Pads() )
  1252. startItems.push_back( pad );
  1253. }
  1254. else if( item->Type() == PCB_GENERATOR_T )
  1255. {
  1256. for( BOARD_ITEM* generatedItem : static_cast<PCB_GENERATOR*>( item )->GetBoardItems() )
  1257. {
  1258. if( BOARD_CONNECTED_ITEM::ClassOf( generatedItem ) )
  1259. startItems.push_back( static_cast<BOARD_CONNECTED_ITEM*>( generatedItem ) );
  1260. }
  1261. }
  1262. else if( BOARD_CONNECTED_ITEM::ClassOf( item ) )
  1263. {
  1264. startItems.push_back( static_cast<BOARD_CONNECTED_ITEM*>( item ) );
  1265. }
  1266. }
  1267. selectAllConnectedTracks( startItems, stopCondition );
  1268. if( m_selection.GetItems().size() > initialCount )
  1269. break;
  1270. }
  1271. m_frame->SetStatusText( wxEmptyString );
  1272. // Inform other potentially interested tools
  1273. m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
  1274. return 0;
  1275. }
  1276. void PCB_SELECTION_TOOL::selectAllConnectedTracks( const std::vector<BOARD_CONNECTED_ITEM*>& aStartItems,
  1277. STOP_CONDITION aStopCondition )
  1278. {
  1279. PROF_TIMER refreshTimer;
  1280. double refreshIntervalMs = 500; // Refresh display with this interval to indicate progress
  1281. int lastSelectionSize = (int) m_selection.GetSize();
  1282. auto connectivity = board()->GetConnectivity();
  1283. std::set<PAD*> startPadSet;
  1284. std::vector<BOARD_CONNECTED_ITEM*> cleanupItems;
  1285. for( BOARD_CONNECTED_ITEM* startItem : aStartItems )
  1286. {
  1287. // Register starting pads
  1288. if( startItem->Type() == PCB_PAD_T )
  1289. startPadSet.insert( static_cast<PAD*>( startItem ) );
  1290. // Select any starting track items
  1291. if( startItem->IsType( { PCB_TRACE_T, PCB_ARC_T, PCB_VIA_T } ) )
  1292. select( startItem );
  1293. }
  1294. for( BOARD_CONNECTED_ITEM* startItem : aStartItems )
  1295. {
  1296. std::map<VECTOR2I, std::vector<PCB_TRACK*>> trackMap;
  1297. std::map<VECTOR2I, PCB_VIA*> viaMap;
  1298. std::map<VECTOR2I, PAD*> padMap;
  1299. std::map<VECTOR2I, std::vector<PCB_SHAPE*>> shapeMap;
  1300. std::vector<std::pair<VECTOR2I, LSET>> activePts;
  1301. if( startItem->HasFlag( SKIP_STRUCT ) ) // Skip already visited items
  1302. continue;
  1303. auto connectedItems = connectivity->GetConnectedItems( startItem,
  1304. EXCLUDE_ZONES | IGNORE_NETS );
  1305. // Build maps of connected items
  1306. for( BOARD_CONNECTED_ITEM* item : connectedItems )
  1307. {
  1308. switch( item->Type() )
  1309. {
  1310. case PCB_ARC_T:
  1311. case PCB_TRACE_T:
  1312. {
  1313. PCB_TRACK* track = static_cast<PCB_TRACK*>( item );
  1314. trackMap[track->GetStart()].push_back( track );
  1315. trackMap[track->GetEnd()].push_back( track );
  1316. break;
  1317. }
  1318. case PCB_VIA_T:
  1319. {
  1320. PCB_VIA* via = static_cast<PCB_VIA*>( item );
  1321. viaMap[via->GetStart()] = via;
  1322. break;
  1323. }
  1324. case PCB_PAD_T:
  1325. {
  1326. PAD* pad = static_cast<PAD*>( item );
  1327. padMap[pad->GetPosition()] = pad;
  1328. break;
  1329. }
  1330. case PCB_SHAPE_T:
  1331. {
  1332. PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( item );
  1333. for( const auto& point : shape->GetConnectionPoints() )
  1334. shapeMap[point].push_back( shape );
  1335. break;
  1336. }
  1337. default:
  1338. break;
  1339. }
  1340. }
  1341. // Set up the initial active points
  1342. switch( startItem->Type() )
  1343. {
  1344. case PCB_ARC_T:
  1345. case PCB_TRACE_T:
  1346. {
  1347. PCB_TRACK* track = static_cast<PCB_TRACK*>( startItem );
  1348. activePts.push_back( { track->GetStart(), track->GetLayerSet() } );
  1349. activePts.push_back( { track->GetEnd(), track->GetLayerSet() } );
  1350. break;
  1351. }
  1352. case PCB_VIA_T:
  1353. activePts.push_back( { startItem->GetPosition(), startItem->GetLayerSet() } );
  1354. break;
  1355. case PCB_PAD_T:
  1356. activePts.push_back( { startItem->GetPosition(), startItem->GetLayerSet() } );
  1357. break;
  1358. case PCB_SHAPE_T:
  1359. {
  1360. PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( startItem );
  1361. for( const auto& point : shape->GetConnectionPoints() )
  1362. activePts.push_back( { point, startItem->GetLayerSet() } );
  1363. break;
  1364. }
  1365. default:
  1366. break;
  1367. }
  1368. bool expand = true;
  1369. int failSafe = 0;
  1370. // Iterative push from all active points
  1371. while( expand && failSafe++ < 100000 )
  1372. {
  1373. expand = false;
  1374. for( int i = (int) activePts.size() - 1; i >= 0; --i )
  1375. {
  1376. VECTOR2I pt = activePts[i].first;
  1377. LSET layerSetCu = activePts[i].second & LSET::AllCuMask();
  1378. auto viaIt = viaMap.find( pt );
  1379. auto padIt = padMap.find( pt );
  1380. bool gotVia = ( viaIt != viaMap.end() )
  1381. && ( layerSetCu & ( viaIt->second->GetLayerSet() ) ).any();
  1382. bool gotPad = ( padIt != padMap.end() )
  1383. && ( layerSetCu & ( padIt->second->GetLayerSet() ) ).any();
  1384. bool gotNonStartPad =
  1385. gotPad && ( startPadSet.find( padIt->second ) == startPadSet.end() );
  1386. if( aStopCondition == STOP_AT_JUNCTION )
  1387. {
  1388. size_t pt_count = 0;
  1389. for( PCB_TRACK* track : trackMap[pt] )
  1390. {
  1391. if( track->GetStart() != track->GetEnd()
  1392. && layerSetCu.Contains( track->GetLayer() ) )
  1393. {
  1394. pt_count++;
  1395. }
  1396. }
  1397. if( pt_count > 2 || gotVia || gotNonStartPad )
  1398. {
  1399. activePts.erase( activePts.begin() + i );
  1400. continue;
  1401. }
  1402. }
  1403. else if( aStopCondition == STOP_AT_PAD )
  1404. {
  1405. if( gotNonStartPad )
  1406. {
  1407. activePts.erase( activePts.begin() + i );
  1408. continue;
  1409. }
  1410. }
  1411. if( gotPad )
  1412. {
  1413. PAD* pad = padIt->second;
  1414. if( !pad->HasFlag( SKIP_STRUCT ) )
  1415. {
  1416. pad->SetFlags( SKIP_STRUCT );
  1417. cleanupItems.push_back( pad );
  1418. activePts.push_back( { pad->GetPosition(), pad->GetLayerSet() } );
  1419. expand = true;
  1420. }
  1421. }
  1422. for( PCB_TRACK* track : trackMap[pt] )
  1423. {
  1424. if( !layerSetCu.Contains( track->GetLayer() ) )
  1425. continue;
  1426. if( !track->IsSelected() )
  1427. select( track );
  1428. if( !track->HasFlag( SKIP_STRUCT ) )
  1429. {
  1430. track->SetFlags( SKIP_STRUCT );
  1431. cleanupItems.push_back( track );
  1432. if( track->GetStart() == pt )
  1433. activePts.push_back( { track->GetEnd(), track->GetLayerSet() } );
  1434. else
  1435. activePts.push_back( { track->GetStart(), track->GetLayerSet() } );
  1436. if( aStopCondition != STOP_AT_SEGMENT )
  1437. expand = true;
  1438. }
  1439. }
  1440. for( PCB_SHAPE* shape : shapeMap[pt] )
  1441. {
  1442. if( !layerSetCu.Contains( shape->GetLayer() ) )
  1443. continue;
  1444. if( !shape->IsSelected() )
  1445. select( shape );
  1446. if( !shape->HasFlag( SKIP_STRUCT ) )
  1447. {
  1448. shape->SetFlags( SKIP_STRUCT );
  1449. cleanupItems.push_back( shape );
  1450. for( const VECTOR2I& newPoint : shape->GetConnectionPoints() )
  1451. {
  1452. if( newPoint == pt )
  1453. continue;
  1454. activePts.push_back( { newPoint, shape->GetLayerSet() } );
  1455. }
  1456. if( aStopCondition != STOP_AT_SEGMENT )
  1457. expand = true;
  1458. }
  1459. }
  1460. if( viaMap.count( pt ) )
  1461. {
  1462. PCB_VIA* via = viaMap[pt];
  1463. if( !via->IsSelected() )
  1464. select( via );
  1465. if( !via->HasFlag( SKIP_STRUCT ) )
  1466. {
  1467. via->SetFlags( SKIP_STRUCT );
  1468. cleanupItems.push_back( via );
  1469. activePts.push_back( { via->GetPosition(), via->GetLayerSet() } );
  1470. if( aStopCondition != STOP_AT_SEGMENT )
  1471. expand = true;
  1472. }
  1473. }
  1474. activePts.erase( activePts.begin() + i );
  1475. }
  1476. // Refresh display for the feel of progress
  1477. if( refreshTimer.msecs() >= refreshIntervalMs )
  1478. {
  1479. if( m_selection.Size() != lastSelectionSize )
  1480. {
  1481. m_frame->GetCanvas()->ForceRefresh();
  1482. lastSelectionSize = m_selection.Size();
  1483. }
  1484. refreshTimer.Start();
  1485. }
  1486. }
  1487. }
  1488. std::set<EDA_ITEM*> toDeselect;
  1489. std::set<EDA_ITEM*> toSelect;
  1490. // Promote generated members to their PCB_GENERATOR parents
  1491. for( EDA_ITEM* item : m_selection )
  1492. {
  1493. if( !item->IsBOARD_ITEM() )
  1494. continue;
  1495. BOARD_ITEM* boardItem = static_cast<BOARD_ITEM*>( item );
  1496. EDA_GROUP* parent = boardItem->GetParentGroup();
  1497. if( parent && parent->AsEdaItem()->Type() == PCB_GENERATOR_T )
  1498. {
  1499. toDeselect.insert( item );
  1500. if( !parent->AsEdaItem()->IsSelected() )
  1501. toSelect.insert( parent->AsEdaItem() );
  1502. }
  1503. }
  1504. for( EDA_ITEM* item : toDeselect )
  1505. unselect( item );
  1506. for( EDA_ITEM* item : toSelect )
  1507. select( item );
  1508. for( BOARD_CONNECTED_ITEM* item : cleanupItems )
  1509. item->ClearFlags( SKIP_STRUCT );
  1510. }
  1511. bool PCB_SELECTION_TOOL::isExpandableGraphicShape( const EDA_ITEM* aItem ) const
  1512. {
  1513. if( aItem->Type() == PCB_SHAPE_T )
  1514. {
  1515. const PCB_SHAPE* shape = static_cast<const PCB_SHAPE*>( aItem );
  1516. switch( shape->GetShape() )
  1517. {
  1518. case SHAPE_T::SEGMENT:
  1519. case SHAPE_T::ARC:
  1520. case SHAPE_T::BEZIER:
  1521. return !shape->IsOnCopperLayer();
  1522. case SHAPE_T::POLY:
  1523. return !shape->IsOnCopperLayer() && !shape->IsClosed();
  1524. default:
  1525. return false;
  1526. }
  1527. }
  1528. return false;
  1529. }
  1530. void PCB_SELECTION_TOOL::selectAllConnectedShapes( const std::vector<PCB_SHAPE*>& aStartItems )
  1531. {
  1532. std::stack<PCB_SHAPE*> toSearch;
  1533. std::set<PCB_SHAPE*> toCleanup;
  1534. for( PCB_SHAPE* startItem : aStartItems )
  1535. toSearch.push( startItem );
  1536. GENERAL_COLLECTOR collector;
  1537. GENERAL_COLLECTORS_GUIDE guide = getCollectorsGuide();
  1538. auto searchPoint = [&]( const VECTOR2I& aWhere )
  1539. {
  1540. collector.Collect( board(), { PCB_SHAPE_T }, aWhere, guide );
  1541. for( EDA_ITEM* item : collector )
  1542. {
  1543. if( isExpandableGraphicShape( item ) )
  1544. toSearch.push( static_cast<PCB_SHAPE*>( item ) );
  1545. }
  1546. };
  1547. while( !toSearch.empty() )
  1548. {
  1549. PCB_SHAPE* shape = toSearch.top();
  1550. toSearch.pop();
  1551. if( shape->HasFlag( SKIP_STRUCT ) )
  1552. continue;
  1553. select( shape );
  1554. shape->SetFlags( SKIP_STRUCT );
  1555. toCleanup.insert( shape );
  1556. guide.SetLayerVisibleBits( shape->GetLayerSet() );
  1557. searchPoint( shape->GetStart() );
  1558. searchPoint( shape->GetEnd() );
  1559. }
  1560. for( PCB_SHAPE* shape : toCleanup )
  1561. shape->ClearFlags( SKIP_STRUCT );
  1562. }
  1563. int PCB_SELECTION_TOOL::selectUnconnected( const TOOL_EVENT& aEvent )
  1564. {
  1565. // Get all pads
  1566. std::vector<PAD*> pads;
  1567. for( EDA_ITEM* item : m_selection.GetItems() )
  1568. {
  1569. if( item->Type() == PCB_FOOTPRINT_T )
  1570. {
  1571. for( PAD* pad : static_cast<FOOTPRINT*>( item )->Pads() )
  1572. pads.push_back( pad );
  1573. }
  1574. else if( item->Type() == PCB_PAD_T )
  1575. {
  1576. pads.push_back( static_cast<PAD*>( item ) );
  1577. }
  1578. }
  1579. // Select every footprint on the end of the ratsnest for each pad in our selection
  1580. std::shared_ptr<CONNECTIVITY_DATA> conn = board()->GetConnectivity();
  1581. for( PAD* pad : pads )
  1582. {
  1583. for( const CN_EDGE& edge : conn->GetRatsnestForPad( pad ) )
  1584. {
  1585. wxCHECK2( edge.GetSourceNode() && !edge.GetSourceNode()->Dirty(), continue );
  1586. wxCHECK2( edge.GetTargetNode() && !edge.GetTargetNode()->Dirty(), continue );
  1587. BOARD_CONNECTED_ITEM* sourceParent = edge.GetSourceNode()->Parent();
  1588. BOARD_CONNECTED_ITEM* targetParent = edge.GetTargetNode()->Parent();
  1589. if( sourceParent == pad )
  1590. {
  1591. if( targetParent->Type() == PCB_PAD_T )
  1592. select( static_cast<PAD*>( targetParent )->GetParent() );
  1593. }
  1594. else if( targetParent == pad )
  1595. {
  1596. if( sourceParent->Type() == PCB_PAD_T )
  1597. select( static_cast<PAD*>( sourceParent )->GetParent() );
  1598. }
  1599. }
  1600. }
  1601. return 0;
  1602. }
  1603. int PCB_SELECTION_TOOL::grabUnconnected( const TOOL_EVENT& aEvent )
  1604. {
  1605. PCB_SELECTION originalSelection = m_selection;
  1606. // Get all pads
  1607. std::vector<PAD*> pads;
  1608. for( EDA_ITEM* item : m_selection.GetItems() )
  1609. {
  1610. if( item->Type() == PCB_FOOTPRINT_T )
  1611. {
  1612. for( PAD* pad : static_cast<FOOTPRINT*>( item )->Pads() )
  1613. pads.push_back( pad );
  1614. }
  1615. else if( item->Type() == PCB_PAD_T )
  1616. {
  1617. pads.push_back( static_cast<PAD*>( item ) );
  1618. }
  1619. }
  1620. ClearSelection();
  1621. // Select every footprint on the end of the ratsnest for each pad in our selection
  1622. std::shared_ptr<CONNECTIVITY_DATA> conn = board()->GetConnectivity();
  1623. for( PAD* pad : pads )
  1624. {
  1625. const std::vector<CN_EDGE> edges = conn->GetRatsnestForPad( pad );
  1626. // Need to have something unconnected to grab
  1627. if( edges.size() == 0 )
  1628. continue;
  1629. double currentDistance = DBL_MAX;
  1630. FOOTPRINT* nearest = nullptr;
  1631. // Check every ratsnest line for the nearest one
  1632. for( const CN_EDGE& edge : edges )
  1633. {
  1634. if( edge.GetSourceNode()->Parent()->GetParentFootprint()
  1635. == edge.GetTargetNode()->Parent()->GetParentFootprint() )
  1636. {
  1637. continue; // This edge is a loop on the same footprint
  1638. }
  1639. // Figure out if we are the source or the target node on the ratnest
  1640. const CN_ANCHOR* other = edge.GetSourceNode()->Parent() == pad ? edge.GetTargetNode().get()
  1641. : edge.GetSourceNode().get();
  1642. wxCHECK2( other && !other->Dirty(), continue );
  1643. // We only want to grab footprints, so the ratnest has to point to a pad
  1644. if( other->Parent()->Type() != PCB_PAD_T )
  1645. continue;
  1646. if( edge.GetLength() < currentDistance )
  1647. {
  1648. currentDistance = edge.GetLength();
  1649. nearest = other->Parent()->GetParentFootprint();
  1650. }
  1651. }
  1652. if( nearest != nullptr )
  1653. select( nearest );
  1654. }
  1655. m_toolMgr->RunAction( PCB_ACTIONS::moveIndividually );
  1656. return 0;
  1657. }
  1658. void PCB_SELECTION_TOOL::SelectAllItemsOnNet( int aNetCode, bool aSelect )
  1659. {
  1660. std::shared_ptr<CONNECTIVITY_DATA> conn = board()->GetConnectivity();
  1661. for( BOARD_ITEM* item : conn->GetNetItems( aNetCode, { PCB_TRACE_T,
  1662. PCB_ARC_T,
  1663. PCB_VIA_T,
  1664. PCB_SHAPE_T } ) )
  1665. {
  1666. if( itemPassesFilter( item, true ) )
  1667. aSelect ? select( item ) : unselect( item );
  1668. }
  1669. }
  1670. int PCB_SELECTION_TOOL::selectNet( const TOOL_EVENT& aEvent )
  1671. {
  1672. bool select = aEvent.IsAction( &PCB_ACTIONS::selectNet );
  1673. // If we've been passed an argument, just select that netcode1
  1674. int netcode = aEvent.Parameter<int>();
  1675. if( netcode > 0 )
  1676. {
  1677. SelectAllItemsOnNet( netcode, select );
  1678. // Inform other potentially interested tools
  1679. if( m_selection.Size() > 0 )
  1680. m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
  1681. else
  1682. m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent );
  1683. return 0;
  1684. }
  1685. if( !selectCursor() )
  1686. return 0;
  1687. // copy the selection, since we're going to iterate and modify
  1688. auto selection = m_selection.GetItems();
  1689. for( EDA_ITEM* i : selection )
  1690. {
  1691. BOARD_CONNECTED_ITEM* connItem = dynamic_cast<BOARD_CONNECTED_ITEM*>( i );
  1692. if( connItem )
  1693. SelectAllItemsOnNet( connItem->GetNetCode(), select );
  1694. }
  1695. // Inform other potentially interested tools
  1696. if( m_selection.Size() > 0 )
  1697. m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
  1698. else
  1699. m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent );
  1700. return 0;
  1701. }
  1702. void PCB_SELECTION_TOOL::selectAllItemsOnSheet( wxString& aSheetPath )
  1703. {
  1704. std::vector<BOARD_ITEM*> footprints;
  1705. // store all footprints that are on that sheet path
  1706. for( FOOTPRINT* footprint : board()->Footprints() )
  1707. {
  1708. if( footprint == nullptr )
  1709. continue;
  1710. wxString footprint_path = footprint->GetPath().AsString().BeforeLast( '/' );
  1711. if( footprint_path.IsEmpty() )
  1712. footprint_path += '/';
  1713. if( footprint_path == aSheetPath )
  1714. footprints.push_back( footprint );
  1715. }
  1716. for( BOARD_ITEM* i : footprints )
  1717. {
  1718. if( i != nullptr )
  1719. select( i );
  1720. }
  1721. selectConnections( footprints );
  1722. }
  1723. void PCB_SELECTION_TOOL::selectConnections( const std::vector<BOARD_ITEM*>& aItems )
  1724. {
  1725. // Generate a list of all pads, and of all nets they belong to.
  1726. std::list<int> netcodeList;
  1727. std::vector<BOARD_CONNECTED_ITEM*> padList;
  1728. for( BOARD_ITEM* item : aItems )
  1729. {
  1730. switch( item->Type() )
  1731. {
  1732. case PCB_FOOTPRINT_T:
  1733. {
  1734. for( PAD* pad : static_cast<FOOTPRINT*>( item )->Pads() )
  1735. {
  1736. if( pad->IsConnected() )
  1737. {
  1738. netcodeList.push_back( pad->GetNetCode() );
  1739. padList.push_back( pad );
  1740. }
  1741. }
  1742. break;
  1743. }
  1744. case PCB_PAD_T:
  1745. {
  1746. PAD* pad = static_cast<PAD*>( item );
  1747. if( pad->IsConnected() )
  1748. {
  1749. netcodeList.push_back( pad->GetNetCode() );
  1750. padList.push_back( pad );
  1751. }
  1752. break;
  1753. }
  1754. default:
  1755. break;
  1756. }
  1757. }
  1758. // Sort for binary search
  1759. std::sort( padList.begin(), padList.end() );
  1760. // remove all duplicates
  1761. netcodeList.sort();
  1762. netcodeList.unique();
  1763. selectAllConnectedTracks( padList, STOP_AT_PAD );
  1764. // now we need to find all footprints that are connected to each of these nets then we need
  1765. // to determine if these footprints are in the list of footprints
  1766. std::vector<int> removeCodeList;
  1767. std::shared_ptr<CONNECTIVITY_DATA> conn = board()->GetConnectivity();
  1768. for( int netCode : netcodeList )
  1769. {
  1770. for( BOARD_CONNECTED_ITEM* pad : conn->GetNetItems( netCode, { PCB_PAD_T } ) )
  1771. {
  1772. if( !std::binary_search( padList.begin(), padList.end(), pad ) )
  1773. {
  1774. // if we cannot find the pad in the padList then we can assume that that pad
  1775. // should not be used, therefore invalidate this netcode.
  1776. removeCodeList.push_back( netCode );
  1777. break;
  1778. }
  1779. }
  1780. }
  1781. for( int removeCode : removeCodeList )
  1782. netcodeList.remove( removeCode );
  1783. std::unordered_set<BOARD_ITEM*> localConnectionList;
  1784. for( int netCode : netcodeList )
  1785. {
  1786. for( BOARD_ITEM* item : conn->GetNetItems( netCode, { PCB_TRACE_T,
  1787. PCB_ARC_T,
  1788. PCB_VIA_T,
  1789. PCB_SHAPE_T } ) )
  1790. {
  1791. localConnectionList.insert( item );
  1792. }
  1793. }
  1794. for( BOARD_ITEM* item : localConnectionList )
  1795. select( item );
  1796. }
  1797. int PCB_SELECTION_TOOL::syncSelection( const TOOL_EVENT& aEvent )
  1798. {
  1799. std::vector<BOARD_ITEM*>* items = aEvent.Parameter<std::vector<BOARD_ITEM*>*>();
  1800. if( items )
  1801. doSyncSelection( *items, false );
  1802. return 0;
  1803. }
  1804. int PCB_SELECTION_TOOL::syncSelectionWithNets( const TOOL_EVENT& aEvent )
  1805. {
  1806. std::vector<BOARD_ITEM*>* items = aEvent.Parameter<std::vector<BOARD_ITEM*>*>();
  1807. if( items )
  1808. doSyncSelection( *items, true );
  1809. return 0;
  1810. }
  1811. void PCB_SELECTION_TOOL::doSyncSelection( const std::vector<BOARD_ITEM*>& aItems, bool aWithNets )
  1812. {
  1813. if( m_selection.Front() && m_selection.Front()->IsMoving() )
  1814. return;
  1815. ClearSelection( true /*quiet mode*/ );
  1816. // Perform individual selection of each item before processing the event.
  1817. for( BOARD_ITEM* item : aItems )
  1818. select( item );
  1819. if( aWithNets )
  1820. selectConnections( aItems );
  1821. BOX2I bbox = m_selection.GetBoundingBox();
  1822. if( bbox.GetWidth() != 0 && bbox.GetHeight() != 0 )
  1823. {
  1824. if( m_frame->GetPcbNewSettings()->m_CrossProbing.center_on_items )
  1825. {
  1826. if( m_frame->GetPcbNewSettings()->m_CrossProbing.zoom_to_fit )
  1827. ZoomFitCrossProbeBBox( bbox );
  1828. m_frame->FocusOnLocation( bbox.Centre() );
  1829. }
  1830. }
  1831. view()->UpdateAllLayersColor();
  1832. m_frame->GetCanvas()->ForceRefresh();
  1833. if( m_selection.Size() > 0 )
  1834. m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
  1835. }
  1836. int PCB_SELECTION_TOOL::selectSheetContents( const TOOL_EVENT& aEvent )
  1837. {
  1838. ClearSelection( true /*quiet mode*/ );
  1839. wxString sheetPath = *aEvent.Parameter<wxString*>();
  1840. selectAllItemsOnSheet( sheetPath );
  1841. zoomFitSelection();
  1842. if( m_selection.Size() > 0 )
  1843. m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
  1844. return 0;
  1845. }
  1846. int PCB_SELECTION_TOOL::selectSameSheet( const TOOL_EVENT& aEvent )
  1847. {
  1848. // this function currently only supports footprints since they are only on one sheet.
  1849. EDA_ITEM* item = m_selection.Front();
  1850. if( !item )
  1851. return 0;
  1852. if( item->Type() != PCB_FOOTPRINT_T )
  1853. return 0;
  1854. FOOTPRINT* footprint = dynamic_cast<FOOTPRINT*>( item );
  1855. if( !footprint || footprint->GetPath().empty() )
  1856. return 0;
  1857. ClearSelection( true /*quiet mode*/ );
  1858. // get the sheet path only.
  1859. wxString sheetPath = footprint->GetPath().AsString().BeforeLast( '/' );
  1860. if( sheetPath.IsEmpty() )
  1861. sheetPath += '/';
  1862. selectAllItemsOnSheet( sheetPath );
  1863. // Inform other potentially interested tools
  1864. if( m_selection.Size() > 0 )
  1865. m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
  1866. return 0;
  1867. }
  1868. void PCB_SELECTION_TOOL::zoomFitSelection()
  1869. {
  1870. // Should recalculate the view to zoom in on the selection.
  1871. BOX2I selectionBox = m_selection.GetBoundingBox();
  1872. KIGFX::VIEW* view = getView();
  1873. VECTOR2D screenSize = view->ToWorld( ToVECTOR2D( m_frame->GetCanvas()->GetClientSize() ),
  1874. false );
  1875. screenSize.x = std::max( 10.0, screenSize.x );
  1876. screenSize.y = std::max( 10.0, screenSize.y );
  1877. if( selectionBox.GetWidth() != 0 || selectionBox.GetHeight() != 0 )
  1878. {
  1879. VECTOR2D vsize = selectionBox.GetSize();
  1880. double scale = view->GetScale() / std::max( fabs( vsize.x / screenSize.x ),
  1881. fabs( vsize.y / screenSize.y ) );
  1882. view->SetScale( scale );
  1883. view->SetCenter( selectionBox.Centre() );
  1884. view->Add( &m_selection );
  1885. }
  1886. m_frame->GetCanvas()->ForceRefresh();
  1887. }
  1888. void PCB_SELECTION_TOOL::ZoomFitCrossProbeBBox( const BOX2I& aBBox )
  1889. {
  1890. // Should recalculate the view to zoom in on the bbox.
  1891. KIGFX::VIEW* view = getView();
  1892. if( aBBox.GetWidth() == 0 )
  1893. return;
  1894. BOX2I bbox = aBBox;
  1895. bbox.Normalize();
  1896. //#define DEFAULT_PCBNEW_CODE // Un-comment for normal full zoom KiCad algorithm
  1897. #ifdef DEFAULT_PCBNEW_CODE
  1898. auto bbSize = bbox.Inflate( bbox.GetWidth() * 0.2f ).GetSize();
  1899. auto screenSize = view->ToWorld( GetCanvas()->GetClientSize(), false );
  1900. // The "fabs" on x ensures the right answer when the view is flipped
  1901. screenSize.x = std::max( 10.0, fabs( screenSize.x ) );
  1902. screenSize.y = std::max( 10.0, screenSize.y );
  1903. double ratio = std::max( fabs( bbSize.x / screenSize.x ), fabs( bbSize.y / screenSize.y ) );
  1904. // Try not to zoom on every cross-probe; it gets very noisy
  1905. if( crossProbingSettings.zoom_to_fit && ( ratio < 0.5 || ratio > 1.0 ) )
  1906. view->SetScale( view->GetScale() / ratio );
  1907. #endif // DEFAULT_PCBNEW_CODE
  1908. #ifndef DEFAULT_PCBNEW_CODE // Do the scaled zoom
  1909. auto bbSize = bbox.Inflate( KiROUND( bbox.GetWidth() * 0.2 ) ).GetSize();
  1910. VECTOR2D screenSize = view->ToWorld( ToVECTOR2D( m_frame->GetCanvas()->GetClientSize() ),
  1911. false );
  1912. // This code tries to come up with a zoom factor that doesn't simply zoom in
  1913. // to the cross probed component, but instead shows a reasonable amount of the
  1914. // circuit around it to provide context. This reduces or eliminates the need
  1915. // to manually change the zoom because it's too close.
  1916. // Using the default text height as a constant to compare against, use the
  1917. // height of the bounding box of visible items for a footprint to figure out
  1918. // if this is a big footprint (like a processor) or a small footprint (like a resistor).
  1919. // This ratio is not useful by itself as a scaling factor. It must be "bent" to
  1920. // provide good scaling at varying component sizes. Bigger components need less
  1921. // scaling than small ones.
  1922. double currTextHeight = pcbIUScale.mmToIU( DEFAULT_TEXT_SIZE );
  1923. double compRatio = bbSize.y / currTextHeight; // Ratio of component to text height
  1924. // This will end up as the scaling factor we apply to "ratio".
  1925. double compRatioBent = 1.0;
  1926. // This is similar to the original KiCad code that scaled the zoom to make sure
  1927. // components were visible on screen. It's simply a ratio of screen size to
  1928. // component size, and its job is to zoom in to make the component fullscreen.
  1929. // Earlier in the code the component BBox is given a 20% margin to add some
  1930. // breathing room. We compare the height of this enlarged component bbox to the
  1931. // default text height. If a component will end up with the sides clipped, we
  1932. // adjust later to make sure it fits on screen.
  1933. //
  1934. // The "fabs" on x ensures the right answer when the view is flipped
  1935. screenSize.x = std::max( 10.0, fabs( screenSize.x ) );
  1936. screenSize.y = std::max( 10.0, screenSize.y );
  1937. double ratio = std::max( -1.0, fabs( bbSize.y / screenSize.y ) );
  1938. // Original KiCad code for how much to scale the zoom
  1939. double kicadRatio = std::max( fabs( bbSize.x / screenSize.x ),
  1940. fabs( bbSize.y / screenSize.y ) );
  1941. // LUT to scale zoom ratio to provide reasonable schematic context. Must work
  1942. // with footprints of varying sizes (e.g. 0402 package and 200 pin BGA).
  1943. // "first" is used as the input and "second" as the output
  1944. //
  1945. // "first" = compRatio (footprint height / default text height)
  1946. // "second" = Amount to scale ratio by
  1947. std::vector<std::pair<double, double>> lut {
  1948. { 1, 8 },
  1949. { 1.5, 5 },
  1950. { 3, 3 },
  1951. { 4.5, 2.5 },
  1952. { 8, 2.0 },
  1953. { 12, 1.7 },
  1954. { 16, 1.5 },
  1955. { 24, 1.3 },
  1956. { 32, 1.0 },
  1957. };
  1958. std::vector<std::pair<double, double>>::iterator it;
  1959. compRatioBent = lut.back().second; // Large component default
  1960. if( compRatio >= lut.front().first )
  1961. {
  1962. // Use LUT to do linear interpolation of "compRatio" within "first", then
  1963. // use that result to linearly interpolate "second" which gives the scaling
  1964. // factor needed.
  1965. for( it = lut.begin(); it < lut.end() - 1; it++ )
  1966. {
  1967. if( it->first <= compRatio && next( it )->first >= compRatio )
  1968. {
  1969. double diffx = compRatio - it->first;
  1970. double diffn = next( it )->first - it->first;
  1971. compRatioBent = it->second + ( next( it )->second - it->second ) * diffx / diffn;
  1972. break; // We have our interpolated value
  1973. }
  1974. }
  1975. }
  1976. else
  1977. {
  1978. compRatioBent = lut.front().second; // Small component default
  1979. }
  1980. // If the width of the part we're probing is bigger than what the screen width will be
  1981. // after the zoom, then punt and use the KiCad zoom algorithm since it guarantees the
  1982. // part's width will be encompassed within the screen. This will apply to parts that
  1983. // are much wider than they are tall.
  1984. if( bbSize.x > screenSize.x * ratio * compRatioBent )
  1985. {
  1986. // Use standard KiCad zoom algorithm for parts too wide to fit screen/
  1987. ratio = kicadRatio;
  1988. compRatioBent = 1.0; // Reset so we don't modify the "KiCad" ratio
  1989. wxLogTrace( "CROSS_PROBE_SCALE",
  1990. "Part TOO WIDE for screen. Using normal KiCad zoom ratio: %1.5f", ratio );
  1991. }
  1992. // Now that "compRatioBent" holds our final scaling factor we apply it to the original
  1993. // fullscreen zoom ratio to arrive at the final ratio itself.
  1994. ratio *= compRatioBent;
  1995. bool alwaysZoom = false; // DEBUG - allows us to minimize zooming or not
  1996. // Try not to zoom on every cross-probe; it gets very noisy
  1997. if( ( ratio < 0.5 || ratio > 1.0 ) || alwaysZoom )
  1998. view->SetScale( view->GetScale() / ratio );
  1999. #endif // ifndef DEFAULT_PCBNEW_CODE
  2000. }
  2001. void PCB_SELECTION_TOOL::FindItem( BOARD_ITEM* aItem )
  2002. {
  2003. bool cleared = false;
  2004. if( m_selection.GetSize() > 0 )
  2005. {
  2006. // Don't fire an event now; most of the time it will be redundant as we're about to
  2007. // fire a SelectedEvent.
  2008. cleared = true;
  2009. ClearSelection( true /*quiet mode*/ );
  2010. }
  2011. if( aItem )
  2012. {
  2013. switch( aItem->Type() )
  2014. {
  2015. case PCB_NETINFO_T:
  2016. {
  2017. int netCode = static_cast<NETINFO_ITEM*>( aItem )->GetNetCode();
  2018. if( netCode > 0 )
  2019. {
  2020. SelectAllItemsOnNet( netCode, true );
  2021. m_frame->FocusOnLocation( aItem->GetCenter() );
  2022. }
  2023. break;
  2024. }
  2025. default:
  2026. select( aItem );
  2027. m_frame->FocusOnLocation( aItem->GetPosition() );
  2028. }
  2029. // If the item has a bounding box, then zoom out if needed
  2030. if( aItem->GetBoundingBox().GetHeight() > 0 && aItem->GetBoundingBox().GetWidth() > 0 )
  2031. {
  2032. // This adds some margin
  2033. double marginFactor = 2;
  2034. KIGFX::PCB_VIEW* pcbView = canvas()->GetView();
  2035. BOX2D screenBox = pcbView->GetViewport();
  2036. VECTOR2D screenSize = screenBox.GetSize();
  2037. BOX2I screenRect = BOX2ISafe( screenBox.GetOrigin(), screenSize / marginFactor );
  2038. if( !screenRect.Contains( aItem->GetBoundingBox() ) )
  2039. {
  2040. double scaleX = screenSize.x /
  2041. static_cast<double>( aItem->GetBoundingBox().GetWidth() );
  2042. double scaleY = screenSize.y /
  2043. static_cast<double>( aItem->GetBoundingBox().GetHeight() );
  2044. scaleX /= marginFactor;
  2045. scaleY /= marginFactor;
  2046. double scale = scaleX > scaleY ? scaleY : scaleX;
  2047. if( scale < 1 ) // Don't zoom in, only zoom out
  2048. {
  2049. pcbView->SetScale( pcbView->GetScale() * ( scale ) );
  2050. //Let's refocus because there is an algorithm to avoid dialogs in there.
  2051. m_frame->FocusOnLocation( aItem->GetCenter() );
  2052. }
  2053. }
  2054. }
  2055. // Inform other potentially interested tools
  2056. m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
  2057. }
  2058. else if( cleared )
  2059. {
  2060. m_toolMgr->ProcessEvent( EVENTS::ClearedEvent );
  2061. }
  2062. m_frame->GetCanvas()->ForceRefresh();
  2063. }
  2064. /**
  2065. * Determine if an item is included by the filter specified.
  2066. *
  2067. * @return true if aItem should be selected by this filter (i..e not filtered out)
  2068. */
  2069. static bool itemIsIncludedByFilter( const BOARD_ITEM& aItem, const BOARD& aBoard,
  2070. const DIALOG_FILTER_SELECTION::OPTIONS& aFilterOptions )
  2071. {
  2072. switch( aItem.Type() )
  2073. {
  2074. case PCB_FOOTPRINT_T:
  2075. {
  2076. const FOOTPRINT& footprint = static_cast<const FOOTPRINT&>( aItem );
  2077. return aFilterOptions.includeModules
  2078. && ( aFilterOptions.includeLockedModules || !footprint.IsLocked() );
  2079. }
  2080. case PCB_TRACE_T:
  2081. case PCB_ARC_T:
  2082. return aFilterOptions.includeTracks;
  2083. case PCB_VIA_T:
  2084. return aFilterOptions.includeVias;
  2085. case PCB_ZONE_T:
  2086. return aFilterOptions.includeZones;
  2087. case PCB_SHAPE_T:
  2088. case PCB_TARGET_T:
  2089. case PCB_DIM_ALIGNED_T:
  2090. case PCB_DIM_CENTER_T:
  2091. case PCB_DIM_RADIAL_T:
  2092. case PCB_DIM_ORTHOGONAL_T:
  2093. case PCB_DIM_LEADER_T:
  2094. if( aItem.GetLayer() == Edge_Cuts )
  2095. return aFilterOptions.includeBoardOutlineLayer;
  2096. else
  2097. return aFilterOptions.includeItemsOnTechLayers;
  2098. case PCB_FIELD_T:
  2099. case PCB_TEXT_T:
  2100. case PCB_TEXTBOX_T:
  2101. case PCB_TABLE_T:
  2102. case PCB_TABLECELL_T:
  2103. return aFilterOptions.includePcbTexts;
  2104. default:
  2105. // Filter dialog is inclusive, not exclusive. If it's not included, then it doesn't
  2106. // get selected.
  2107. return false;
  2108. }
  2109. }
  2110. int PCB_SELECTION_TOOL::filterSelection( const TOOL_EVENT& aEvent )
  2111. {
  2112. const BOARD& board = *getModel<BOARD>();
  2113. DIALOG_FILTER_SELECTION::OPTIONS& opts = m_priv->m_filterOpts;
  2114. DIALOG_FILTER_SELECTION dlg( m_frame, opts );
  2115. const int cmd = dlg.ShowModal();
  2116. if( cmd != wxID_OK )
  2117. return 0;
  2118. // copy current selection
  2119. std::deque<EDA_ITEM*> selection = m_selection.GetItems();
  2120. ClearSelection( true /*quiet mode*/ );
  2121. // re-select items from the saved selection according to the dialog options
  2122. for( EDA_ITEM* i : selection )
  2123. {
  2124. if( !i->IsBOARD_ITEM() )
  2125. continue;
  2126. BOARD_ITEM* item = static_cast<BOARD_ITEM*>( i );
  2127. bool include = itemIsIncludedByFilter( *item, board, opts );
  2128. if( include )
  2129. select( item );
  2130. }
  2131. m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
  2132. return 0;
  2133. }
  2134. void PCB_SELECTION_TOOL::FilterCollectedItems( GENERAL_COLLECTOR& aCollector, bool aMultiSelect )
  2135. {
  2136. if( aCollector.GetCount() == 0 )
  2137. return;
  2138. std::set<BOARD_ITEM*> rejected;
  2139. for( EDA_ITEM* i : aCollector )
  2140. {
  2141. if( !i->IsBOARD_ITEM() )
  2142. continue;
  2143. BOARD_ITEM* item = static_cast<BOARD_ITEM*>( i );
  2144. if( !itemPassesFilter( item, aMultiSelect ) )
  2145. rejected.insert( item );
  2146. }
  2147. for( BOARD_ITEM* item : rejected )
  2148. aCollector.Remove( item );
  2149. }
  2150. bool PCB_SELECTION_TOOL::itemPassesFilter( BOARD_ITEM* aItem, bool aMultiSelect )
  2151. {
  2152. if( !m_filter.lockedItems )
  2153. {
  2154. if( aItem->IsLocked() || ( aItem->GetParent() && aItem->GetParent()->IsLocked() ) )
  2155. {
  2156. if( aItem->Type() == PCB_PAD_T && !aMultiSelect )
  2157. {
  2158. // allow a single pad to be selected -- there are a lot of operations that
  2159. // require this so we allow this one inconsistency
  2160. }
  2161. else
  2162. {
  2163. return false;
  2164. }
  2165. }
  2166. }
  2167. if( !aItem )
  2168. return false;
  2169. KICAD_T itemType = aItem->Type();
  2170. if( itemType == PCB_GENERATOR_T )
  2171. {
  2172. if( static_cast<PCB_GENERATOR*>( aItem )->GetItems().empty() )
  2173. {
  2174. if( !m_filter.otherItems )
  2175. return false;
  2176. }
  2177. else
  2178. {
  2179. itemType = ( *static_cast<PCB_GENERATOR*>( aItem )->GetItems().begin() )->Type();
  2180. }
  2181. }
  2182. switch( itemType )
  2183. {
  2184. case PCB_FOOTPRINT_T:
  2185. if( !m_filter.footprints )
  2186. return false;
  2187. break;
  2188. case PCB_PAD_T:
  2189. if( !m_filter.pads )
  2190. return false;
  2191. break;
  2192. case PCB_TRACE_T:
  2193. case PCB_ARC_T:
  2194. if( !m_filter.tracks )
  2195. return false;
  2196. break;
  2197. case PCB_VIA_T:
  2198. if( !m_filter.vias )
  2199. return false;
  2200. break;
  2201. case PCB_ZONE_T:
  2202. {
  2203. ZONE* zone = static_cast<ZONE*>( aItem );
  2204. if( ( !m_filter.zones && !zone->GetIsRuleArea() )
  2205. || ( !m_filter.keepouts && zone->GetIsRuleArea() ) )
  2206. {
  2207. return false;
  2208. }
  2209. // m_SolderMaskBridges zone is a special zone, only used to showsolder mask briges
  2210. // after running DRC. it is not really a board item.
  2211. // Never select it or delete by a Commit.
  2212. if( zone == m_frame->GetBoard()->m_SolderMaskBridges )
  2213. return false;
  2214. break;
  2215. }
  2216. case PCB_SHAPE_T:
  2217. case PCB_TARGET_T:
  2218. if( !m_filter.graphics )
  2219. return false;
  2220. break;
  2221. case PCB_REFERENCE_IMAGE_T:
  2222. if( !m_filter.graphics )
  2223. return false;
  2224. // a reference image living in a footprint must not be selected inside the board editor
  2225. if( !m_isFootprintEditor && aItem->GetParentFootprint() )
  2226. return false;
  2227. break;
  2228. case PCB_FIELD_T:
  2229. case PCB_TEXT_T:
  2230. case PCB_TEXTBOX_T:
  2231. case PCB_TABLE_T:
  2232. case PCB_TABLECELL_T:
  2233. if( !m_filter.text )
  2234. return false;
  2235. break;
  2236. case PCB_DIM_ALIGNED_T:
  2237. case PCB_DIM_CENTER_T:
  2238. case PCB_DIM_RADIAL_T:
  2239. case PCB_DIM_ORTHOGONAL_T:
  2240. case PCB_DIM_LEADER_T:
  2241. if( !m_filter.dimensions )
  2242. return false;
  2243. break;
  2244. default:
  2245. if( !m_filter.otherItems )
  2246. return false;
  2247. }
  2248. return true;
  2249. }
  2250. void PCB_SELECTION_TOOL::ClearSelection( bool aQuietMode )
  2251. {
  2252. if( m_selection.Empty() )
  2253. return;
  2254. while( m_selection.GetSize() )
  2255. unhighlight( m_selection.Front(), SELECTED, &m_selection );
  2256. view()->Update( &m_selection );
  2257. m_selection.SetIsHover( false );
  2258. m_selection.ClearReferencePoint();
  2259. // Inform other potentially interested tools
  2260. if( !aQuietMode )
  2261. {
  2262. m_toolMgr->ProcessEvent( EVENTS::ClearedEvent );
  2263. m_toolMgr->RunAction( PCB_ACTIONS::hideLocalRatsnest );
  2264. }
  2265. }
  2266. void PCB_SELECTION_TOOL::RebuildSelection()
  2267. {
  2268. m_selection.Clear();
  2269. bool enteredGroupFound = false;
  2270. INSPECTOR_FUNC inspector =
  2271. [&]( EDA_ITEM* item, void* testData )
  2272. {
  2273. if( item->IsSelected() )
  2274. {
  2275. EDA_ITEM* parent = item->GetParent();
  2276. // Let selected parents handle their children.
  2277. if( parent && parent->IsSelected() )
  2278. return INSPECT_RESULT::CONTINUE;
  2279. highlight( item, SELECTED, &m_selection );
  2280. }
  2281. if( item->Type() == PCB_GROUP_T )
  2282. {
  2283. if( item == m_enteredGroup )
  2284. {
  2285. item->SetFlags( ENTERED );
  2286. enteredGroupFound = true;
  2287. }
  2288. else
  2289. {
  2290. item->ClearFlags( ENTERED );
  2291. }
  2292. }
  2293. return INSPECT_RESULT::CONTINUE;
  2294. };
  2295. board()->Visit( inspector, nullptr, m_isFootprintEditor ? GENERAL_COLLECTOR::FootprintItems
  2296. : GENERAL_COLLECTOR::AllBoardItems );
  2297. if( !enteredGroupFound )
  2298. {
  2299. m_enteredGroupOverlay.Clear();
  2300. m_enteredGroup = nullptr;
  2301. }
  2302. }
  2303. bool PCB_SELECTION_TOOL::Selectable( const BOARD_ITEM* aItem, bool checkVisibilityOnly ) const
  2304. {
  2305. const RENDER_SETTINGS* settings = getView()->GetPainter()->GetSettings();
  2306. const PCB_DISPLAY_OPTIONS& options = frame()->GetDisplayOptions();
  2307. auto visibleLayers =
  2308. [&]() -> LSET
  2309. {
  2310. if( m_isFootprintEditor )
  2311. {
  2312. LSET set;
  2313. for( PCB_LAYER_ID layer : LSET::AllLayersMask() )
  2314. set.set( layer, view()->IsLayerVisible( layer ) );
  2315. return set;
  2316. }
  2317. else
  2318. {
  2319. return board()->GetVisibleLayers();
  2320. }
  2321. };
  2322. auto layerVisible =
  2323. [&]( PCB_LAYER_ID aLayer )
  2324. {
  2325. if( m_isFootprintEditor )
  2326. return view()->IsLayerVisible( aLayer );
  2327. else
  2328. return board()->IsLayerVisible( aLayer );
  2329. };
  2330. if( settings->GetHighContrast() )
  2331. {
  2332. const std::set<int> activeLayers = settings->GetHighContrastLayers();
  2333. bool onActiveLayer = false;
  2334. for( int layer : activeLayers )
  2335. {
  2336. // NOTE: Only checking the regular layers (not GAL meta-layers)
  2337. if( layer < PCB_LAYER_ID_COUNT && aItem->IsOnLayer( ToLAYER_ID( layer ) ) )
  2338. {
  2339. onActiveLayer = true;
  2340. break;
  2341. }
  2342. }
  2343. if( !onActiveLayer && aItem->Type() != PCB_MARKER_T )
  2344. {
  2345. // We do not want to select items that are in the background
  2346. return false;
  2347. }
  2348. }
  2349. if( aItem->Type() == PCB_FOOTPRINT_T )
  2350. {
  2351. const FOOTPRINT* footprint = static_cast<const FOOTPRINT*>( aItem );
  2352. // In footprint editor, we do not want to select the footprint itself.
  2353. if( m_isFootprintEditor )
  2354. return false;
  2355. // Allow selection of footprints if some part of the footprint is visible.
  2356. if( footprint->GetSide() != UNDEFINED_LAYER && !m_skip_heuristics )
  2357. {
  2358. LSET boardSide = footprint->IsFlipped() ? LSET::BackMask() : LSET::FrontMask();
  2359. if( !( visibleLayers() & boardSide ).any() )
  2360. return false;
  2361. }
  2362. // If the footprint has no items except the reference and value fields, include the
  2363. // footprint in the selections.
  2364. if( footprint->GraphicalItems().empty()
  2365. && footprint->Pads().empty()
  2366. && footprint->Zones().empty() )
  2367. {
  2368. return true;
  2369. }
  2370. for( const BOARD_ITEM* item : footprint->GraphicalItems() )
  2371. {
  2372. if( Selectable( item, true ) )
  2373. return true;
  2374. }
  2375. for( const PAD* pad : footprint->Pads() )
  2376. {
  2377. if( Selectable( pad, true ) )
  2378. return true;
  2379. }
  2380. for( const ZONE* zone : footprint->Zones() )
  2381. {
  2382. if( Selectable( zone, true ) )
  2383. return true;
  2384. }
  2385. return false;
  2386. }
  2387. else if( aItem->Type() == PCB_GROUP_T )
  2388. {
  2389. PCB_GROUP* group = const_cast<PCB_GROUP*>( static_cast<const PCB_GROUP*>( aItem ) );
  2390. // Similar to logic for footprint, a group is selectable if any of its members are.
  2391. // (This recurses.)
  2392. for( BOARD_ITEM* item : group->GetBoardItems() )
  2393. {
  2394. if( Selectable( item, true ) )
  2395. return true;
  2396. }
  2397. return false;
  2398. }
  2399. if( aItem->GetParentGroup() && aItem->GetParentGroup()->AsEdaItem()->Type() == PCB_GENERATOR_T )
  2400. return false;
  2401. const ZONE* zone = nullptr;
  2402. const PCB_VIA* via = nullptr;
  2403. const PAD* pad = nullptr;
  2404. const PCB_TEXT* text = nullptr;
  2405. const PCB_FIELD* field = nullptr;
  2406. const PCB_MARKER* marker = nullptr;
  2407. // Most footprint children can only be selected in the footprint editor.
  2408. if( aItem->GetParentFootprint() && !m_isFootprintEditor && !checkVisibilityOnly )
  2409. {
  2410. if( aItem->Type() != PCB_FIELD_T && aItem->Type() != PCB_PAD_T
  2411. && aItem->Type() != PCB_TEXT_T )
  2412. {
  2413. return false;
  2414. }
  2415. }
  2416. switch( aItem->Type() )
  2417. {
  2418. case PCB_ZONE_T:
  2419. if( !board()->IsElementVisible( LAYER_ZONES ) || ( options.m_ZoneOpacity == 0.00 ) )
  2420. return false;
  2421. zone = static_cast<const ZONE*>( aItem );
  2422. // A teardrop is modelled as a property of a via, pad or the board (for track-to-track
  2423. // teardrops). The underlying zone is only an implementation detail.
  2424. if( zone->IsTeardropArea() && !board()->LegacyTeardrops() )
  2425. return false;
  2426. // zones can exist on multiple layers!
  2427. if( !( zone->GetLayerSet() & visibleLayers() ).any() )
  2428. return false;
  2429. break;
  2430. case PCB_TRACE_T:
  2431. case PCB_ARC_T:
  2432. if( !board()->IsElementVisible( LAYER_TRACKS ) || ( options.m_TrackOpacity == 0.00 ) )
  2433. return false;
  2434. if( !layerVisible( aItem->GetLayer() ) )
  2435. return false;
  2436. break;
  2437. case PCB_VIA_T:
  2438. if( !board()->IsElementVisible( LAYER_VIAS ) || ( options.m_ViaOpacity == 0.00 ) )
  2439. return false;
  2440. via = static_cast<const PCB_VIA*>( aItem );
  2441. // For vias it is enough if only one of its layers is visible
  2442. if( !( visibleLayers() & via->GetLayerSet() ).any() )
  2443. return false;
  2444. break;
  2445. case PCB_FIELD_T:
  2446. field = static_cast<const PCB_FIELD*>( aItem );
  2447. if( !field->IsVisible() )
  2448. return false;
  2449. if( field->IsReference() && !view()->IsLayerVisible( LAYER_FP_REFERENCES ) )
  2450. return false;
  2451. if( field->IsValue() && !view()->IsLayerVisible( LAYER_FP_VALUES ) )
  2452. return false;
  2453. // Handle all other fields with normal text visibility controls
  2454. KI_FALLTHROUGH;
  2455. case PCB_TEXT_T:
  2456. text = static_cast<const PCB_TEXT*>( aItem );
  2457. if( !layerVisible( text->GetLayer() ) )
  2458. return false;
  2459. // Apply the LOD visibility test as well
  2460. if( !view()->IsVisible( text ) )
  2461. return false;
  2462. if( aItem->GetParentFootprint() )
  2463. {
  2464. int controlLayer = LAYER_FP_TEXT;
  2465. if( text->GetText() == wxT( "${REFERENCE}" ) )
  2466. controlLayer = LAYER_FP_REFERENCES;
  2467. else if( text->GetText() == wxT( "${VALUE}" ) )
  2468. controlLayer = LAYER_FP_VALUES;
  2469. if( !view()->IsLayerVisible( controlLayer ) )
  2470. return false;
  2471. }
  2472. break;
  2473. case PCB_REFERENCE_IMAGE_T:
  2474. if( options.m_ImageOpacity == 0.00 )
  2475. return false;
  2476. // Bitmap images on board are hidden if LAYER_DRAW_BITMAPS is not visible
  2477. if( !view()->IsLayerVisible( LAYER_DRAW_BITMAPS ) )
  2478. return false;
  2479. KI_FALLTHROUGH;
  2480. case PCB_SHAPE_T:
  2481. if( options.m_FilledShapeOpacity == 0.0 && static_cast<const PCB_SHAPE*>( aItem )->IsAnyFill() )
  2482. return false;
  2483. KI_FALLTHROUGH;
  2484. case PCB_TEXTBOX_T:
  2485. case PCB_TABLE_T:
  2486. case PCB_TABLECELL_T:
  2487. if( !layerVisible( aItem->GetLayer() ) )
  2488. return false;
  2489. if( aItem->Type() == PCB_TABLECELL_T )
  2490. {
  2491. const PCB_TABLECELL* cell = static_cast<const PCB_TABLECELL*>( aItem );
  2492. if( cell->GetRowSpan() == 0 || cell->GetColSpan() == 0 )
  2493. return false;
  2494. }
  2495. break;
  2496. case PCB_DIM_ALIGNED_T:
  2497. case PCB_DIM_LEADER_T:
  2498. case PCB_DIM_CENTER_T:
  2499. case PCB_DIM_RADIAL_T:
  2500. case PCB_DIM_ORTHOGONAL_T:
  2501. if( !layerVisible( aItem->GetLayer() ) )
  2502. return false;
  2503. break;
  2504. case PCB_PAD_T:
  2505. if( options.m_PadOpacity == 0.00 )
  2506. return false;
  2507. pad = static_cast<const PAD*>( aItem );
  2508. if( pad->GetAttribute() == PAD_ATTRIB::PTH || pad->GetAttribute() == PAD_ATTRIB::NPTH )
  2509. {
  2510. // A pad's hole is visible on every layer the pad is visible on plus many layers the
  2511. // pad is not visible on -- so we only need to check for any visible hole layers.
  2512. if( !( visibleLayers() & LSET::PhysicalLayersMask() ).any() )
  2513. return false;
  2514. }
  2515. else
  2516. {
  2517. if( !( pad->GetLayerSet() & visibleLayers() ).any() )
  2518. return false;
  2519. }
  2520. break;
  2521. case PCB_MARKER_T:
  2522. marker = static_cast<const PCB_MARKER*>( aItem );
  2523. if( marker && marker->IsExcluded() && !board()->IsElementVisible( LAYER_DRC_EXCLUSION ) )
  2524. return false;
  2525. break;
  2526. // These are not selectable
  2527. case PCB_NETINFO_T:
  2528. case NOT_USED:
  2529. case TYPE_NOT_INIT:
  2530. return false;
  2531. default: // Suppress warnings
  2532. break;
  2533. }
  2534. return true;
  2535. }
  2536. void PCB_SELECTION_TOOL::select( EDA_ITEM* aItem )
  2537. {
  2538. if( !aItem || aItem->IsSelected() || !aItem->IsBOARD_ITEM() )
  2539. return;
  2540. if( aItem->Type() == PCB_PAD_T )
  2541. {
  2542. FOOTPRINT* footprint = static_cast<FOOTPRINT*>( aItem->GetParent() );
  2543. if( m_selection.Contains( footprint ) )
  2544. return;
  2545. }
  2546. if( m_enteredGroup &&
  2547. !PCB_GROUP::WithinScope( static_cast<BOARD_ITEM*>( aItem ), m_enteredGroup,
  2548. m_isFootprintEditor ) )
  2549. {
  2550. ExitGroup();
  2551. }
  2552. highlight( aItem, SELECTED, &m_selection );
  2553. }
  2554. void PCB_SELECTION_TOOL::unselect( EDA_ITEM* aItem )
  2555. {
  2556. unhighlight( aItem, SELECTED, &m_selection );
  2557. }
  2558. void PCB_SELECTION_TOOL::highlight( EDA_ITEM* aItem, int aMode, SELECTION* aGroup )
  2559. {
  2560. if( aGroup )
  2561. aGroup->Add( aItem );
  2562. highlightInternal( aItem, aMode, aGroup != nullptr );
  2563. view()->Update( aItem, KIGFX::REPAINT );
  2564. // Many selections are very temporal and updating the display each time just
  2565. // creates noise.
  2566. if( aMode == BRIGHTENED )
  2567. getView()->MarkTargetDirty( KIGFX::TARGET_OVERLAY );
  2568. }
  2569. void PCB_SELECTION_TOOL::highlightInternal( EDA_ITEM* aItem, int aMode, bool aUsingOverlay )
  2570. {
  2571. if( aMode == SELECTED )
  2572. aItem->SetSelected();
  2573. else if( aMode == BRIGHTENED )
  2574. aItem->SetBrightened();
  2575. if( aUsingOverlay && aMode != BRIGHTENED )
  2576. view()->Hide( aItem, true ); // Hide the original item, so it is shown only on overlay
  2577. if( aItem->IsBOARD_ITEM() )
  2578. {
  2579. BOARD_ITEM* boardItem = static_cast<BOARD_ITEM*>( aItem );
  2580. boardItem->RunOnChildren( std::bind( &PCB_SELECTION_TOOL::highlightInternal, this, _1, aMode, aUsingOverlay ),
  2581. RECURSE_MODE::RECURSE );
  2582. }
  2583. }
  2584. void PCB_SELECTION_TOOL::unhighlight( EDA_ITEM* aItem, int aMode, SELECTION* aGroup )
  2585. {
  2586. if( aGroup )
  2587. aGroup->Remove( aItem );
  2588. unhighlightInternal( aItem, aMode, aGroup != nullptr );
  2589. view()->Update( aItem, KIGFX::REPAINT );
  2590. // Many selections are very temporal and updating the display each time just creates noise.
  2591. if( aMode == BRIGHTENED )
  2592. getView()->MarkTargetDirty( KIGFX::TARGET_OVERLAY );
  2593. }
  2594. void PCB_SELECTION_TOOL::unhighlightInternal( EDA_ITEM* aItem, int aMode, bool aUsingOverlay )
  2595. {
  2596. if( aMode == SELECTED )
  2597. aItem->ClearSelected();
  2598. else if( aMode == BRIGHTENED )
  2599. aItem->ClearBrightened();
  2600. if( aUsingOverlay && aMode != BRIGHTENED )
  2601. {
  2602. view()->Hide( aItem, false ); // Restore original item visibility...
  2603. view()->Update( aItem ); // ... and make sure it's redrawn un-selected
  2604. }
  2605. if( aItem->IsBOARD_ITEM() )
  2606. {
  2607. BOARD_ITEM* boardItem = static_cast<BOARD_ITEM*>( aItem );
  2608. boardItem->RunOnChildren( std::bind( &PCB_SELECTION_TOOL::unhighlightInternal, this, _1, aMode, aUsingOverlay ),
  2609. RECURSE_MODE::RECURSE );
  2610. }
  2611. }
  2612. bool PCB_SELECTION_TOOL::selectionContains( const VECTOR2I& aPoint ) const
  2613. {
  2614. const unsigned GRIP_MARGIN = 20;
  2615. int margin = KiROUND( getView()->ToWorld( GRIP_MARGIN ) );
  2616. // Check if the point is located close to any of the currently selected items
  2617. for( EDA_ITEM* item : m_selection )
  2618. {
  2619. if( !item->IsBOARD_ITEM() )
  2620. continue;
  2621. BOX2I itemBox = item->ViewBBox();
  2622. itemBox.Inflate( margin ); // Give some margin for gripping an item
  2623. if( itemBox.Contains( aPoint ) )
  2624. {
  2625. if( item->HitTest( aPoint, margin ) )
  2626. return true;
  2627. bool found = false;
  2628. if( PCB_GROUP* group = dynamic_cast<PCB_GROUP*>( item ) )
  2629. {
  2630. group->RunOnChildren(
  2631. [&]( BOARD_ITEM* aItem )
  2632. {
  2633. if( aItem->HitTest( aPoint, margin ) )
  2634. found = true;
  2635. },
  2636. RECURSE_MODE::RECURSE );
  2637. }
  2638. if( found )
  2639. return true;
  2640. }
  2641. }
  2642. return false;
  2643. }
  2644. int PCB_SELECTION_TOOL::hitTestDistance( const VECTOR2I& aWhere, BOARD_ITEM* aItem,
  2645. int aMaxDistance ) const
  2646. {
  2647. BOX2D viewportD = getView()->GetViewport();
  2648. BOX2I viewport = BOX2ISafe( viewportD );
  2649. int distance = INT_MAX;
  2650. SEG loc( aWhere, aWhere );
  2651. switch( aItem->Type() )
  2652. {
  2653. case PCB_FIELD_T:
  2654. case PCB_TEXT_T:
  2655. {
  2656. PCB_TEXT* text = static_cast<PCB_TEXT*>( aItem );
  2657. // Add a bit of slop to text-shapes
  2658. if( text->GetEffectiveTextShape()->Collide( loc, aMaxDistance, &distance ) )
  2659. distance = std::clamp( distance - ( aMaxDistance / 2 ), 0, distance );
  2660. break;
  2661. }
  2662. case PCB_TEXTBOX_T:
  2663. case PCB_TABLECELL_T:
  2664. {
  2665. PCB_TEXTBOX* textbox = static_cast<PCB_TEXTBOX*>( aItem );
  2666. // Add a bit of slop to text-shapes
  2667. if( textbox->GetEffectiveTextShape()->Collide( loc, aMaxDistance, &distance ) )
  2668. distance = std::clamp( distance - ( aMaxDistance / 2 ), 0, distance );
  2669. break;
  2670. }
  2671. case PCB_TABLE_T:
  2672. {
  2673. PCB_TABLE* table = static_cast<PCB_TABLE*>( aItem );
  2674. for( PCB_TABLECELL* cell : table->GetCells() )
  2675. {
  2676. // Add a bit of slop to text-shapes
  2677. if( cell->GetEffectiveTextShape()->Collide( loc, aMaxDistance, &distance ) )
  2678. distance = std::clamp( distance - ( aMaxDistance / 2 ), 0, distance );
  2679. }
  2680. break;
  2681. }
  2682. case PCB_ZONE_T:
  2683. {
  2684. ZONE* zone = static_cast<ZONE*>( aItem );
  2685. // Zone borders are very specific
  2686. if( zone->HitTestForEdge( aWhere, aMaxDistance / 2 ) )
  2687. distance = 0;
  2688. else if( zone->HitTestForEdge( aWhere, aMaxDistance ) )
  2689. distance = aMaxDistance / 2;
  2690. else
  2691. aItem->GetEffectiveShape()->Collide( loc, aMaxDistance, &distance );
  2692. break;
  2693. }
  2694. case PCB_FOOTPRINT_T:
  2695. {
  2696. FOOTPRINT* footprint = static_cast<FOOTPRINT*>( aItem );
  2697. BOX2I bbox = footprint->GetBoundingBox( false );
  2698. try
  2699. {
  2700. footprint->GetBoundingHull().Collide( loc, aMaxDistance, &distance );
  2701. }
  2702. catch( const std::exception& e )
  2703. {
  2704. wxFAIL_MSG( wxString::Format( wxT( "Clipper exception occurred: %s" ), e.what() ) );
  2705. }
  2706. // Consider footprints larger than the viewport only as a last resort
  2707. if( bbox.GetHeight() > viewport.GetHeight() || bbox.GetWidth() > viewport.GetWidth() )
  2708. distance = INT_MAX / 2;
  2709. break;
  2710. }
  2711. case PCB_MARKER_T:
  2712. {
  2713. PCB_MARKER* marker = static_cast<PCB_MARKER*>( aItem );
  2714. SHAPE_LINE_CHAIN polygon;
  2715. marker->ShapeToPolygon( polygon );
  2716. polygon.Move( marker->GetPos() );
  2717. polygon.Collide( loc, aMaxDistance, &distance );
  2718. break;
  2719. }
  2720. case PCB_GROUP_T:
  2721. case PCB_GENERATOR_T:
  2722. {
  2723. PCB_GROUP* group = static_cast<PCB_GROUP*>( aItem );
  2724. for( BOARD_ITEM* member : group->GetBoardItems() )
  2725. distance = std::min( distance, hitTestDistance( aWhere, member, aMaxDistance ) );
  2726. break;
  2727. }
  2728. case PCB_PAD_T:
  2729. {
  2730. static_cast<PAD*>( aItem )->Padstack().ForEachUniqueLayer(
  2731. [&]( PCB_LAYER_ID aLayer )
  2732. {
  2733. int layerDistance = INT_MAX;
  2734. aItem->GetEffectiveShape( aLayer )->Collide( loc, aMaxDistance, &layerDistance );
  2735. distance = std::min( distance, layerDistance );
  2736. } );
  2737. break;
  2738. }
  2739. default:
  2740. aItem->GetEffectiveShape()->Collide( loc, aMaxDistance, &distance );
  2741. break;
  2742. }
  2743. return distance;
  2744. }
  2745. void PCB_SELECTION_TOOL::pruneObscuredSelectionCandidates( GENERAL_COLLECTOR& aCollector ) const
  2746. {
  2747. wxCHECK( m_frame, /* void */ );
  2748. if( aCollector.GetCount() < 2 )
  2749. return;
  2750. const RENDER_SETTINGS* settings = getView()->GetPainter()->GetSettings();
  2751. wxCHECK( settings, /* void */ );
  2752. PCB_LAYER_ID activeLayer = m_frame->GetActiveLayer();
  2753. LSET visibleLayers = m_frame->GetBoard()->GetVisibleLayers();
  2754. LSET enabledLayers = m_frame->GetBoard()->GetEnabledLayers();
  2755. LSEQ enabledLayerStack = enabledLayers.SeqStackupTop2Bottom( activeLayer );
  2756. wxCHECK( !enabledLayerStack.empty(), /* void */ );
  2757. auto isZoneFillKeepout =
  2758. []( const BOARD_ITEM* aItem ) -> bool
  2759. {
  2760. if( aItem->Type() == PCB_ZONE_T )
  2761. {
  2762. const ZONE* zone = static_cast<const ZONE*>( aItem );
  2763. if( zone->GetIsRuleArea() && zone->GetDoNotAllowZoneFills() )
  2764. return true;
  2765. }
  2766. return false;
  2767. };
  2768. std::vector<LAYER_OPACITY_ITEM> opacityStackup;
  2769. for( int i = 0; i < aCollector.GetCount(); i++ )
  2770. {
  2771. const BOARD_ITEM* item = aCollector[i];
  2772. LSET itemLayers = item->GetLayerSet() & enabledLayers & visibleLayers;
  2773. LSEQ itemLayerSeq = itemLayers.Seq( enabledLayerStack );
  2774. for( PCB_LAYER_ID layer : itemLayerSeq )
  2775. {
  2776. COLOR4D color = settings->GetColor( item, layer );
  2777. if( color.a == 0 )
  2778. continue;
  2779. LAYER_OPACITY_ITEM opacityItem;
  2780. opacityItem.m_Layer = layer;
  2781. opacityItem.m_Opacity = color.a;
  2782. opacityItem.m_Item = item;
  2783. if( isZoneFillKeepout( item ) )
  2784. opacityItem.m_Opacity = 0.0;
  2785. opacityStackup.emplace_back( opacityItem );
  2786. }
  2787. }
  2788. std::sort( opacityStackup.begin(), opacityStackup.end(),
  2789. [&]( const LAYER_OPACITY_ITEM& aLhs, const LAYER_OPACITY_ITEM& aRhs ) -> bool
  2790. {
  2791. int retv = enabledLayerStack.TestLayers( aLhs.m_Layer, aRhs.m_Layer );
  2792. if( retv )
  2793. return retv > 0;
  2794. return aLhs.m_Opacity > aRhs.m_Opacity;
  2795. } );
  2796. std::set<const BOARD_ITEM*> visibleItems;
  2797. std::set<const BOARD_ITEM*> itemsToRemove;
  2798. double minAlphaLimit = ADVANCED_CFG::GetCfg().m_PcbSelectionVisibilityRatio;
  2799. double currentStackupOpacity = 0.0;
  2800. PCB_LAYER_ID lastVisibleLayer = PCB_LAYER_ID::UNDEFINED_LAYER;
  2801. for( const LAYER_OPACITY_ITEM& opacityItem : opacityStackup )
  2802. {
  2803. if( lastVisibleLayer == PCB_LAYER_ID::UNDEFINED_LAYER )
  2804. {
  2805. currentStackupOpacity = opacityItem.m_Opacity;
  2806. lastVisibleLayer = opacityItem.m_Layer;
  2807. visibleItems.emplace( opacityItem.m_Item );
  2808. continue;
  2809. }
  2810. // Objects to ignore and fallback to the old selection behavior.
  2811. auto ignoreItem =
  2812. [&]()
  2813. {
  2814. const BOARD_ITEM* item = opacityItem.m_Item;
  2815. wxCHECK( item, false );
  2816. // Check items that span multiple layers for visibility.
  2817. if( visibleItems.count( item ) )
  2818. return true;
  2819. // Don't prune child items of a footprint that is already visible.
  2820. if( item->GetParent()
  2821. && ( item->GetParent()->Type() == PCB_FOOTPRINT_T )
  2822. && visibleItems.count( item->GetParent() ) )
  2823. {
  2824. return true;
  2825. }
  2826. // Keepout zones are transparent but for some reason, PCB_PAINTER::GetColor()
  2827. // returns the color of the zone it prevents from filling.
  2828. if( isZoneFillKeepout( item ) )
  2829. return true;
  2830. return false;
  2831. };
  2832. // Everything on the currently selected layer is visible;
  2833. if( opacityItem.m_Layer == enabledLayerStack[0] )
  2834. {
  2835. visibleItems.emplace( opacityItem.m_Item );
  2836. }
  2837. else
  2838. {
  2839. double itemVisibility = opacityItem.m_Opacity * ( 1.0 - currentStackupOpacity );
  2840. if( ( itemVisibility <= minAlphaLimit ) && !ignoreItem() )
  2841. itemsToRemove.emplace( opacityItem.m_Item );
  2842. else
  2843. visibleItems.emplace( opacityItem.m_Item );
  2844. }
  2845. if( opacityItem.m_Layer != lastVisibleLayer )
  2846. {
  2847. currentStackupOpacity += opacityItem.m_Opacity * ( 1.0 - currentStackupOpacity );
  2848. currentStackupOpacity = std::min( currentStackupOpacity, 1.0 );
  2849. lastVisibleLayer = opacityItem.m_Layer;
  2850. }
  2851. }
  2852. for( const BOARD_ITEM* itemToRemove : itemsToRemove )
  2853. {
  2854. wxCHECK( aCollector.GetCount() > 1, /* void */ );
  2855. aCollector.Remove( itemToRemove );
  2856. }
  2857. }
  2858. // The general idea here is that if the user clicks directly on a small item inside a larger
  2859. // one, then they want the small item. The quintessential case of this is clicking on a pad
  2860. // within a footprint, but we also apply it for text within a footprint, footprints within
  2861. // larger footprints, and vias within either larger pads or longer tracks.
  2862. //
  2863. // These "guesses" presume there is area within the larger item to click in to select it. If
  2864. // an item is mostly covered by smaller items within it, then the guesses are inappropriate as
  2865. // there might not be any area left to click to select the larger item. In this case we must
  2866. // leave the items in the collector and bring up a Selection Clarification menu.
  2867. //
  2868. // We currently check for pads and text mostly covering a footprint, but we don't check for
  2869. // smaller footprints mostly covering a larger footprint.
  2870. //
  2871. void PCB_SELECTION_TOOL::GuessSelectionCandidates( GENERAL_COLLECTOR& aCollector,
  2872. const VECTOR2I& aWhere ) const
  2873. {
  2874. static const LSET silkLayers( { B_SilkS, F_SilkS } );
  2875. static const LSET courtyardLayers( { B_CrtYd, F_CrtYd } );
  2876. static std::vector<KICAD_T> singleLayerSilkTypes = { PCB_FIELD_T,
  2877. PCB_TEXT_T, PCB_TEXTBOX_T,
  2878. PCB_TABLE_T, PCB_TABLECELL_T,
  2879. PCB_SHAPE_T };
  2880. if( ADVANCED_CFG::GetCfg().m_PcbSelectionVisibilityRatio != 1.0 )
  2881. pruneObscuredSelectionCandidates( aCollector );
  2882. if( aCollector.GetCount() == 1 )
  2883. return;
  2884. std::set<BOARD_ITEM*> preferred;
  2885. std::set<BOARD_ITEM*> rejected;
  2886. VECTOR2I where( aWhere.x, aWhere.y );
  2887. const RENDER_SETTINGS* settings = getView()->GetPainter()->GetSettings();
  2888. PCB_LAYER_ID activeLayer = m_frame->GetActiveLayer();
  2889. // If a silk layer is in front, we assume the user is working with silk and give preferential
  2890. // treatment to single-layer items on *either* silk layer.
  2891. if( silkLayers[activeLayer] )
  2892. {
  2893. for( int i = 0; i < aCollector.GetCount(); ++i )
  2894. {
  2895. BOARD_ITEM* item = aCollector[i];
  2896. if( item->IsType( singleLayerSilkTypes ) && silkLayers[ item->GetLayer() ] )
  2897. preferred.insert( item );
  2898. }
  2899. }
  2900. // Similarly, if a courtyard layer is in front, we assume the user is positioning footprints
  2901. // and give preferential treatment to footprints on *both* top and bottom.
  2902. else if( courtyardLayers[activeLayer] && settings->GetHighContrast() )
  2903. {
  2904. for( int i = 0; i < aCollector.GetCount(); ++i )
  2905. {
  2906. BOARD_ITEM* item = aCollector[i];
  2907. if( item->Type() == PCB_FOOTPRINT_T )
  2908. preferred.insert( item );
  2909. }
  2910. }
  2911. if( preferred.size() > 0 )
  2912. {
  2913. aCollector.Empty();
  2914. for( BOARD_ITEM* item : preferred )
  2915. aCollector.Append( item );
  2916. if( preferred.size() == 1 )
  2917. return;
  2918. }
  2919. // Prefer exact hits to sloppy ones
  2920. constexpr int MAX_SLOP = 5;
  2921. int singlePixel = KiROUND( aCollector.GetGuide()->OnePixelInIU() );
  2922. int maxSlop = KiROUND( MAX_SLOP * aCollector.GetGuide()->OnePixelInIU() );
  2923. int minSlop = INT_MAX;
  2924. std::map<BOARD_ITEM*, int> itemsBySloppiness;
  2925. for( int i = 0; i < aCollector.GetCount(); ++i )
  2926. {
  2927. BOARD_ITEM* item = aCollector[i];
  2928. int itemSlop = hitTestDistance( where, item, maxSlop );
  2929. itemsBySloppiness[ item ] = itemSlop;
  2930. if( itemSlop < minSlop )
  2931. minSlop = itemSlop;
  2932. }
  2933. // Prune sloppier items
  2934. if( minSlop < INT_MAX )
  2935. {
  2936. for( std::pair<BOARD_ITEM*, int> pair : itemsBySloppiness )
  2937. {
  2938. if( pair.second > minSlop + singlePixel )
  2939. aCollector.Transfer( pair.first );
  2940. }
  2941. }
  2942. // If the user clicked on a small item within a much larger one then it's pretty clear
  2943. // they're trying to select the smaller one.
  2944. constexpr double sizeRatio = 1.5;
  2945. std::vector<std::pair<BOARD_ITEM*, double>> itemsByArea;
  2946. for( int i = 0; i < aCollector.GetCount(); ++i )
  2947. {
  2948. BOARD_ITEM* item = aCollector[i];
  2949. double area = 0.0;
  2950. if( item->Type() == PCB_ZONE_T
  2951. && static_cast<ZONE*>( item )->HitTestForEdge( where, maxSlop / 2 ) )
  2952. {
  2953. // Zone borders are very specific, so make them "small"
  2954. area = (double) SEG::Square( singlePixel ) * MAX_SLOP;
  2955. }
  2956. else if( item->Type() == PCB_VIA_T )
  2957. {
  2958. // Vias rarely hide other things, and we don't want them deferring to short track
  2959. // segments underneath them -- so artificially reduce their size from πr² to r².
  2960. area = (double) SEG::Square( static_cast<PCB_VIA*>( item )->GetDrill() / 2 );
  2961. }
  2962. else if( item->Type() == PCB_REFERENCE_IMAGE_T )
  2963. {
  2964. BOX2I box = item->GetBoundingBox();
  2965. area = (double) box.GetWidth() * box.GetHeight();
  2966. }
  2967. else
  2968. {
  2969. try
  2970. {
  2971. area = FOOTPRINT::GetCoverageArea( item, aCollector );
  2972. }
  2973. catch( const std::exception& e )
  2974. {
  2975. wxFAIL_MSG( wxString::Format( wxT( "Clipper exception occurred: %s" ), e.what() ) );
  2976. }
  2977. }
  2978. itemsByArea.emplace_back( item, area );
  2979. }
  2980. std::sort( itemsByArea.begin(), itemsByArea.end(),
  2981. []( const std::pair<BOARD_ITEM*, double>& lhs,
  2982. const std::pair<BOARD_ITEM*, double>& rhs ) -> bool
  2983. {
  2984. return lhs.second < rhs.second;
  2985. } );
  2986. bool rejecting = false;
  2987. for( int i = 1; i < (int) itemsByArea.size(); ++i )
  2988. {
  2989. if( itemsByArea[i].second > itemsByArea[i-1].second * sizeRatio )
  2990. rejecting = true;
  2991. if( rejecting )
  2992. rejected.insert( itemsByArea[i].first );
  2993. }
  2994. // Special case: if a footprint is completely covered with other features then there's no
  2995. // way to select it -- so we need to leave it in the list for user disambiguation.
  2996. constexpr double maxCoverRatio = 0.70;
  2997. for( int i = 0; i < aCollector.GetCount(); ++i )
  2998. {
  2999. if( FOOTPRINT* footprint = dynamic_cast<FOOTPRINT*>( aCollector[i] ) )
  3000. {
  3001. if( footprint->CoverageRatio( aCollector ) > maxCoverRatio )
  3002. rejected.erase( footprint );
  3003. }
  3004. }
  3005. // Hopefully we've now got what the user wanted.
  3006. if( (unsigned) aCollector.GetCount() > rejected.size() ) // do not remove everything
  3007. {
  3008. for( BOARD_ITEM* item : rejected )
  3009. aCollector.Transfer( item );
  3010. }
  3011. // Finally, what we are left with is a set of items of similar coverage area. We now reject
  3012. // any that are not on the active layer, to reduce the number of disambiguation menus shown.
  3013. // If the user wants to force-disambiguate, they can either switch layers or use the modifier
  3014. // key to force the menu.
  3015. if( aCollector.GetCount() > 1 )
  3016. {
  3017. bool haveItemOnActive = false;
  3018. rejected.clear();
  3019. for( int i = 0; i < aCollector.GetCount(); ++i )
  3020. {
  3021. if( !aCollector[i]->IsOnLayer( activeLayer ) )
  3022. rejected.insert( aCollector[i] );
  3023. else
  3024. haveItemOnActive = true;
  3025. }
  3026. if( haveItemOnActive )
  3027. {
  3028. for( BOARD_ITEM* item : rejected )
  3029. aCollector.Transfer( item );
  3030. }
  3031. }
  3032. }
  3033. void PCB_SELECTION_TOOL::FilterCollectorForHierarchy( GENERAL_COLLECTOR& aCollector,
  3034. bool aMultiselect ) const
  3035. {
  3036. std::unordered_set<EDA_ITEM*> toAdd;
  3037. // Set CANDIDATE on all parents which are included in the GENERAL_COLLECTOR. This
  3038. // algorithm is O(3n), whereas checking for the parent inclusion could potentially be O(n^2).
  3039. for( int j = 0; j < aCollector.GetCount(); j++ )
  3040. {
  3041. if( aCollector[j]->GetParent() )
  3042. aCollector[j]->GetParent()->ClearFlags( CANDIDATE );
  3043. if( aCollector[j]->GetParentFootprint() )
  3044. aCollector[j]->GetParentFootprint()->ClearFlags( CANDIDATE );
  3045. }
  3046. if( aMultiselect )
  3047. {
  3048. for( int j = 0; j < aCollector.GetCount(); j++ )
  3049. aCollector[j]->SetFlags( CANDIDATE );
  3050. }
  3051. for( int j = 0; j < aCollector.GetCount(); )
  3052. {
  3053. BOARD_ITEM* item = aCollector[j];
  3054. FOOTPRINT* fp = item->GetParentFootprint();
  3055. BOARD_ITEM* start = item;
  3056. if( !m_isFootprintEditor && fp )
  3057. start = fp;
  3058. // If a group is entered, disallow selections of objects outside the group.
  3059. if( m_enteredGroup && !PCB_GROUP::WithinScope( item, m_enteredGroup, m_isFootprintEditor ) )
  3060. {
  3061. aCollector.Remove( item );
  3062. continue;
  3063. }
  3064. // If any element is a member of a group, replace those elements with the top containing
  3065. // group.
  3066. if( EDA_GROUP* top = PCB_GROUP::TopLevelGroup( start, m_enteredGroup, m_isFootprintEditor ) )
  3067. {
  3068. if( top->AsEdaItem() != item )
  3069. {
  3070. toAdd.insert( top->AsEdaItem() );
  3071. top->AsEdaItem()->SetFlags( CANDIDATE );
  3072. aCollector.Remove( item );
  3073. continue;
  3074. }
  3075. }
  3076. // Footprints are a bit easier as they can't be nested.
  3077. if( fp && ( fp->GetFlags() & CANDIDATE ) )
  3078. {
  3079. // Remove children of selected items
  3080. aCollector.Remove( item );
  3081. continue;
  3082. }
  3083. ++j;
  3084. }
  3085. for( EDA_ITEM* item : toAdd )
  3086. {
  3087. if( !aCollector.HasItem( item ) )
  3088. aCollector.Append( item );
  3089. }
  3090. }
  3091. void PCB_SELECTION_TOOL::FilterCollectorForTableCells( GENERAL_COLLECTOR& aCollector ) const
  3092. {
  3093. std::set<BOARD_ITEM*> to_add;
  3094. // Iterate from the back so we don't have to worry about removals.
  3095. for( int i = (int) aCollector.GetCount() - 1; i >= 0; --i )
  3096. {
  3097. BOARD_ITEM* item = aCollector[i];
  3098. if( item->Type() == PCB_TABLECELL_T )
  3099. {
  3100. if( !aCollector.HasItem( item->GetParent() ) )
  3101. to_add.insert( item->GetParent() );
  3102. aCollector.Remove( item );
  3103. }
  3104. }
  3105. for( BOARD_ITEM* item : to_add )
  3106. aCollector.Append( item );
  3107. }
  3108. void PCB_SELECTION_TOOL::FilterCollectorForFreePads( GENERAL_COLLECTOR& aCollector,
  3109. bool aForcePromotion ) const
  3110. {
  3111. std::set<BOARD_ITEM*> to_add;
  3112. // Iterate from the back so we don't have to worry about removals.
  3113. for( int i = aCollector.GetCount() - 1; i >= 0; --i )
  3114. {
  3115. BOARD_ITEM* item = aCollector[i];
  3116. if( !m_isFootprintEditor && item->Type() == PCB_PAD_T
  3117. && ( !frame()->GetPcbNewSettings()->m_AllowFreePads || aForcePromotion ) )
  3118. {
  3119. if( !aCollector.HasItem( item->GetParent() ) )
  3120. to_add.insert( item->GetParent() );
  3121. aCollector.Remove( item );
  3122. }
  3123. }
  3124. for( BOARD_ITEM* item : to_add )
  3125. aCollector.Append( item );
  3126. }
  3127. void PCB_SELECTION_TOOL::FilterCollectorForMarkers( GENERAL_COLLECTOR& aCollector ) const
  3128. {
  3129. // Iterate from the back so we don't have to worry about removals.
  3130. for( int i = aCollector.GetCount() - 1; i >= 0; --i )
  3131. {
  3132. BOARD_ITEM* item = aCollector[i];
  3133. if( item->Type() == PCB_MARKER_T )
  3134. aCollector.Remove( item );
  3135. }
  3136. }
  3137. void PCB_SELECTION_TOOL::FilterCollectorForFootprints( GENERAL_COLLECTOR& aCollector,
  3138. const VECTOR2I& aWhere ) const
  3139. {
  3140. const RENDER_SETTINGS* settings = getView()->GetPainter()->GetSettings();
  3141. BOX2D viewport = getView()->GetViewport();
  3142. BOX2I extents = BOX2ISafe( viewport );
  3143. bool need_direct_hit = false;
  3144. FOOTPRINT* single_fp = nullptr;
  3145. // If the designer is not modifying the existing selection AND we already have
  3146. // a selection, then we only want to select items that are directly under the cursor.
  3147. // This prevents us from being unable to clear the selection when zoomed into a footprint
  3148. if( !m_additive && !m_subtractive && !m_exclusive_or && m_selection.GetSize() > 0 )
  3149. {
  3150. need_direct_hit = true;
  3151. for( EDA_ITEM* item : m_selection )
  3152. {
  3153. FOOTPRINT* fp = nullptr;
  3154. if( item->Type() == PCB_FOOTPRINT_T )
  3155. fp = static_cast<FOOTPRINT*>( item );
  3156. else if( item->IsBOARD_ITEM() )
  3157. fp = static_cast<BOARD_ITEM*>( item )->GetParentFootprint();
  3158. // If the selection contains items that are not footprints, then don't restrict
  3159. // whether we deselect the item or not.
  3160. if( !fp )
  3161. {
  3162. single_fp = nullptr;
  3163. break;
  3164. }
  3165. else if( !single_fp )
  3166. {
  3167. single_fp = fp;
  3168. }
  3169. // If the selection contains items from multiple footprints, then don't restrict
  3170. // whether we deselect the item or not.
  3171. else if( single_fp != fp )
  3172. {
  3173. single_fp = nullptr;
  3174. break;
  3175. }
  3176. }
  3177. }
  3178. auto visibleLayers =
  3179. [&]() -> LSET
  3180. {
  3181. if( m_isFootprintEditor )
  3182. {
  3183. LSET set;
  3184. for( PCB_LAYER_ID layer : LSET::AllLayersMask() )
  3185. set.set( layer, view()->IsLayerVisible( layer ) );
  3186. return set;
  3187. }
  3188. else
  3189. {
  3190. return board()->GetVisibleLayers();
  3191. }
  3192. };
  3193. LSET layers = visibleLayers();
  3194. if( settings->GetHighContrast() )
  3195. {
  3196. layers.reset();
  3197. const std::set<int> activeLayers = settings->GetHighContrastLayers();
  3198. for( int layer : activeLayers )
  3199. {
  3200. if( layer >= 0 && layer < PCB_LAYER_ID_COUNT )
  3201. layers.set( layer );
  3202. }
  3203. }
  3204. // Iterate from the back so we don't have to worry about removals.
  3205. for( int i = aCollector.GetCount() - 1; i >= 0; --i )
  3206. {
  3207. BOARD_ITEM* item = aCollector[i];
  3208. FOOTPRINT* fp = dyn_cast<FOOTPRINT*>( item );
  3209. if( !fp )
  3210. continue;
  3211. // Make footprints not difficult to select in high-contrast modes.
  3212. if( layers[fp->GetLayer()] )
  3213. continue;
  3214. BOX2I bbox = fp->GetLayerBoundingBox( layers );
  3215. // If the point clicked is not inside the visible bounding box, we can also remove it.
  3216. if( !bbox.Contains( aWhere) )
  3217. aCollector.Remove( item );
  3218. bool has_hit = false;
  3219. for( PCB_LAYER_ID layer : layers )
  3220. {
  3221. if( fp->HitTestOnLayer( extents, false, layer ) )
  3222. {
  3223. has_hit = true;
  3224. break;
  3225. }
  3226. }
  3227. // If the point is outside of the visible bounding box, we can remove it.
  3228. if( !has_hit )
  3229. {
  3230. aCollector.Remove( item );
  3231. }
  3232. // Do not require a direct hit on this fp if the existing selection only contains
  3233. // this fp's items. This allows you to have a selection of pads from a single
  3234. // footprint and still click in the center of the footprint to select it.
  3235. else if( single_fp )
  3236. {
  3237. if( fp == single_fp )
  3238. continue;
  3239. }
  3240. else if( need_direct_hit )
  3241. {
  3242. has_hit = false;
  3243. for( PCB_LAYER_ID layer : layers )
  3244. {
  3245. if( fp->HitTestOnLayer( aWhere, layer ) )
  3246. {
  3247. has_hit = true;
  3248. break;
  3249. }
  3250. }
  3251. if( !has_hit )
  3252. aCollector.Remove( item );
  3253. }
  3254. }
  3255. }
  3256. int PCB_SELECTION_TOOL::updateSelection( const TOOL_EVENT& aEvent )
  3257. {
  3258. getView()->Update( &m_selection );
  3259. getView()->Update( &m_enteredGroupOverlay );
  3260. return 0;
  3261. }
  3262. int PCB_SELECTION_TOOL::SelectColumns( const TOOL_EVENT& aEvent )
  3263. {
  3264. std::set<std::pair<PCB_TABLE*, int>> columns;
  3265. bool added = false;
  3266. for( EDA_ITEM* item : m_selection )
  3267. {
  3268. if( PCB_TABLECELL* cell = dynamic_cast<PCB_TABLECELL*>( item ) )
  3269. {
  3270. PCB_TABLE* table = static_cast<PCB_TABLE*>( cell->GetParent() );
  3271. columns.insert( std::make_pair( table, cell->GetColumn() ) );
  3272. }
  3273. }
  3274. for( auto& [ table, col ] : columns )
  3275. {
  3276. for( int row = 0; row < table->GetRowCount(); ++row )
  3277. {
  3278. PCB_TABLECELL* cell = table->GetCell( row, col );
  3279. if( !cell->IsSelected() )
  3280. {
  3281. select( table->GetCell( row, col ) );
  3282. added = true;
  3283. }
  3284. }
  3285. }
  3286. if( added )
  3287. m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
  3288. return 0;
  3289. }
  3290. int PCB_SELECTION_TOOL::SelectRows( const TOOL_EVENT& aEvent )
  3291. {
  3292. std::set<std::pair<PCB_TABLE*, int>> rows;
  3293. bool added = false;
  3294. for( EDA_ITEM* item : m_selection )
  3295. {
  3296. if( PCB_TABLECELL* cell = dynamic_cast<PCB_TABLECELL*>( item ) )
  3297. {
  3298. PCB_TABLE* table = static_cast<PCB_TABLE*>( cell->GetParent() );
  3299. rows.insert( std::make_pair( table, cell->GetRow() ) );
  3300. }
  3301. }
  3302. for( auto& [ table, row ] : rows )
  3303. {
  3304. for( int col = 0; col < table->GetColCount(); ++col )
  3305. {
  3306. PCB_TABLECELL* cell = table->GetCell( row, col );
  3307. if( !cell->IsSelected() )
  3308. {
  3309. select( table->GetCell( row, col ) );
  3310. added = true;
  3311. }
  3312. }
  3313. }
  3314. if( added )
  3315. m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
  3316. return 0;
  3317. }
  3318. int PCB_SELECTION_TOOL::SelectTable( const TOOL_EVENT& aEvent )
  3319. {
  3320. std::set<PCB_TABLE*> tables;
  3321. bool added = false;
  3322. for( EDA_ITEM* item : m_selection )
  3323. {
  3324. if( PCB_TABLECELL* cell = dynamic_cast<PCB_TABLECELL*>( item ) )
  3325. tables.insert( static_cast<PCB_TABLE*>( cell->GetParent() ) );
  3326. }
  3327. ClearSelection();
  3328. for( PCB_TABLE* table : tables )
  3329. {
  3330. if( !table->IsSelected() )
  3331. {
  3332. select( table );
  3333. added = true;
  3334. }
  3335. }
  3336. if( added )
  3337. m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
  3338. return 0;
  3339. }
  3340. void PCB_SELECTION_TOOL::setTransitions()
  3341. {
  3342. Go( &PCB_SELECTION_TOOL::UpdateMenu, ACTIONS::updateMenu.MakeEvent() );
  3343. Go( &PCB_SELECTION_TOOL::Main, ACTIONS::selectionActivate.MakeEvent() );
  3344. Go( &PCB_SELECTION_TOOL::CursorSelection, ACTIONS::selectionCursor.MakeEvent() );
  3345. Go( &PCB_SELECTION_TOOL::ClearSelection, ACTIONS::selectionClear.MakeEvent() );
  3346. Go( &PCB_SELECTION_TOOL::AddItemToSel, ACTIONS::selectItem.MakeEvent() );
  3347. Go( &PCB_SELECTION_TOOL::AddItemsToSel, ACTIONS::selectItems.MakeEvent() );
  3348. Go( &PCB_SELECTION_TOOL::RemoveItemFromSel, ACTIONS::unselectItem.MakeEvent() );
  3349. Go( &PCB_SELECTION_TOOL::RemoveItemsFromSel, ACTIONS::unselectItems.MakeEvent() );
  3350. Go( &PCB_SELECTION_TOOL::ReselectItem, ACTIONS::reselectItem.MakeEvent() );
  3351. Go( &PCB_SELECTION_TOOL::SelectionMenu, ACTIONS::selectionMenu.MakeEvent() );
  3352. Go( &PCB_SELECTION_TOOL::filterSelection, PCB_ACTIONS::filterSelection.MakeEvent() );
  3353. Go( &PCB_SELECTION_TOOL::expandConnection, PCB_ACTIONS::selectConnection.MakeEvent() );
  3354. Go( &PCB_SELECTION_TOOL::unrouteSelected, PCB_ACTIONS::unrouteSelected.MakeEvent() );
  3355. Go( &PCB_SELECTION_TOOL::unrouteSegment, PCB_ACTIONS::unrouteSegment.MakeEvent() );
  3356. Go( &PCB_SELECTION_TOOL::selectNet, PCB_ACTIONS::selectNet.MakeEvent() );
  3357. Go( &PCB_SELECTION_TOOL::selectNet, PCB_ACTIONS::deselectNet.MakeEvent() );
  3358. Go( &PCB_SELECTION_TOOL::selectUnconnected, PCB_ACTIONS::selectUnconnected.MakeEvent() );
  3359. Go( &PCB_SELECTION_TOOL::grabUnconnected, PCB_ACTIONS::grabUnconnected.MakeEvent() );
  3360. Go( &PCB_SELECTION_TOOL::syncSelection, PCB_ACTIONS::syncSelection.MakeEvent() );
  3361. Go( &PCB_SELECTION_TOOL::syncSelectionWithNets,
  3362. PCB_ACTIONS::syncSelectionWithNets.MakeEvent() );
  3363. Go( &PCB_SELECTION_TOOL::selectSameSheet, PCB_ACTIONS::selectSameSheet.MakeEvent() );
  3364. Go( &PCB_SELECTION_TOOL::selectSheetContents,
  3365. PCB_ACTIONS::selectOnSheetFromEeschema.MakeEvent() );
  3366. Go( &PCB_SELECTION_TOOL::updateSelection, EVENTS::SelectedItemsModified );
  3367. Go( &PCB_SELECTION_TOOL::updateSelection, EVENTS::SelectedItemsMoved );
  3368. Go( &PCB_SELECTION_TOOL::SelectColumns, ACTIONS::selectColumns.MakeEvent() );
  3369. Go( &PCB_SELECTION_TOOL::SelectRows, ACTIONS::selectRows.MakeEvent() );
  3370. Go( &PCB_SELECTION_TOOL::SelectTable, ACTIONS::selectTable.MakeEvent() );
  3371. Go( &PCB_SELECTION_TOOL::SelectRectArea, ACTIONS::selectInsideRectangle.MakeEvent() );
  3372. Go( &PCB_SELECTION_TOOL::SelectRectArea, ACTIONS::selectTouchingRectangle.MakeEvent() );
  3373. Go( &PCB_SELECTION_TOOL::SelectAll, ACTIONS::selectAll.MakeEvent() );
  3374. Go( &PCB_SELECTION_TOOL::UnselectAll, ACTIONS::unselectAll.MakeEvent() );
  3375. Go( &PCB_SELECTION_TOOL::disambiguateCursor, EVENTS::DisambiguatePoint );
  3376. }
  3377. PCB_LASSO_SELECTION_TOOL::PCB_LASSO_SELECTION_TOOL() :
  3378. PCB_TOOL_BASE( "common.InteractiveLassoSelection" )
  3379. {
  3380. }
  3381. PCB_LASSO_SELECTION_TOOL::~PCB_LASSO_SELECTION_TOOL()
  3382. {
  3383. }
  3384. bool PCB_LASSO_SELECTION_TOOL::Init()
  3385. {
  3386. return true;
  3387. }
  3388. void PCB_LASSO_SELECTION_TOOL::Reset( RESET_REASON aReason )
  3389. {
  3390. }
  3391. int PCB_LASSO_SELECTION_TOOL::SelectPolyArea( const TOOL_EVENT& aEvent )
  3392. {
  3393. bool cancelled = false; // Was the tool canceled while it was running?
  3394. bool fixedMode = false;
  3395. bool additive = false;
  3396. bool subtractive = false;
  3397. bool exclusiveOr = false;
  3398. PCB_SELECTION_TOOL* selectionTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
  3399. SELECTION_MODE selectionMode = SELECTION_MODE::TOUCHING_LASSO;
  3400. auto updateModifiersAndCursor =
  3401. [&]( wxTimerEvent& aEvent )
  3402. {
  3403. KICURSOR cursor;
  3404. wxMouseState keyboardState = wxGetMouseState();
  3405. subtractive = keyboardState.ControlDown() && keyboardState.ShiftDown();
  3406. additive = !keyboardState.ControlDown() && keyboardState.ShiftDown();
  3407. exclusiveOr = keyboardState.ControlDown() && !keyboardState.ShiftDown();
  3408. if( additive )
  3409. cursor = KICURSOR::ADD;
  3410. else if( subtractive )
  3411. cursor = KICURSOR::SUBTRACT;
  3412. else if( exclusiveOr )
  3413. cursor = KICURSOR::XOR ;
  3414. else if( selectionMode == SELECTION_MODE::INSIDE_LASSO )
  3415. cursor = KICURSOR::SELECT_WINDOW;
  3416. else
  3417. cursor = KICURSOR::SELECT_LASSO;
  3418. frame()->GetCanvas()->SetCurrentCursor( cursor );
  3419. };
  3420. // No events are sent for modifier keys, so we need to poll them using a timer.
  3421. wxTimer timer;
  3422. timer.Bind( wxEVT_TIMER, updateModifiersAndCursor );
  3423. timer.Start( 100 );
  3424. if( aEvent.HasParameter() )
  3425. {
  3426. fixedMode = true;
  3427. selectionMode = aEvent.Parameter<SELECTION_MODE>();
  3428. }
  3429. SHAPE_LINE_CHAIN points;
  3430. if( selectionMode != SELECTION_MODE::TOUCHING_PATH )
  3431. points.SetClosed( true );
  3432. KIGFX::PREVIEW::SELECTION_AREA area;
  3433. view()->Add( &area );
  3434. view()->SetVisible( &area, true );
  3435. controls()->SetAutoPan( true );
  3436. frame()->PushTool( aEvent );
  3437. Activate();
  3438. while( TOOL_EVENT* evt = Wait() )
  3439. {
  3440. if( !fixedMode )
  3441. {
  3442. // Auto Mode: The selection mode depends on the drawing direction of the selection shape:
  3443. // - Clockwise: Contained selection
  3444. // - Counterclockwise: Touching selection
  3445. double shapeArea = area.GetPoly().Area( false );
  3446. bool isClockwise = shapeArea > 0 ? true : false;
  3447. // Flip the selection mode if the view is mirrored, but only if the area is non-zero.
  3448. // A zero area means the selection shape is a line, so the mode should always be "touching".
  3449. if( view()->IsMirroredX() && shapeArea != 0 )
  3450. isClockwise = !isClockwise;
  3451. selectionMode = isClockwise ? SELECTION_MODE::INSIDE_LASSO
  3452. : SELECTION_MODE::TOUCHING_LASSO;
  3453. }
  3454. if( evt->IsCancelInteractive() || evt->IsActivate() )
  3455. {
  3456. // Cancel the selection
  3457. cancelled = true;
  3458. evt->SetPassEvent( false );
  3459. break;
  3460. }
  3461. else if( evt->IsDrag( BUT_LEFT ) // Lasso selection
  3462. || evt->IsClick( BUT_LEFT ) // Polygon selection
  3463. || evt->IsAction( &ACTIONS::cursorClick ) ) // Return key
  3464. {
  3465. // Add a point to the selection shape
  3466. points.Append( evt->Position() );
  3467. }
  3468. else if( evt->IsDblClick( BUT_LEFT )
  3469. || evt->IsAction( &ACTIONS::cursorDblClick ) // End key
  3470. || evt->IsAction( &ACTIONS::finishInteractive ) )
  3471. {
  3472. // Finish the selection
  3473. area.GetPoly().GenerateBBoxCache();
  3474. selectionTool->SelectMultiple( area, subtractive, exclusiveOr );
  3475. evt->SetPassEvent( false );
  3476. break;
  3477. }
  3478. else if( evt->IsAction( &PCB_ACTIONS::deleteLastPoint )
  3479. || evt->IsAction( &ACTIONS::doDelete )
  3480. || evt->IsAction( &ACTIONS::undo ) )
  3481. {
  3482. // Delete the last point in the selection shape
  3483. if( points.GetPointCount() > 0 )
  3484. {
  3485. controls()->SetCursorPosition( points.CLastPoint() );
  3486. points.Remove( points.GetPointCount() - 1 );
  3487. }
  3488. }
  3489. else
  3490. {
  3491. // Allow some actions for navigation
  3492. passEvent( evt, allowedActions );
  3493. }
  3494. if( points.PointCount() > 0 )
  3495. {
  3496. // Clear existing selection if not in add/sub/xor mode
  3497. if( !additive && !subtractive && !exclusiveOr )
  3498. {
  3499. if( !selectionTool->GetSelection().Empty() )
  3500. {
  3501. selectionTool->ClearSelection( true /*quiet mode*/ );
  3502. m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent );
  3503. }
  3504. }
  3505. }
  3506. // Draw selection shape
  3507. area.SetPoly( points );
  3508. area.GetPoly().Append( m_toolMgr->GetMousePosition() );
  3509. area.SetAdditive( additive );
  3510. area.SetSubtractive( subtractive );
  3511. area.SetExclusiveOr( exclusiveOr );
  3512. area.SetMode( selectionMode );
  3513. view()->Update( &area );
  3514. }
  3515. frame()->PopTool( aEvent );
  3516. controls()->SetAutoPan( false );
  3517. view()->SetVisible( &area, false );
  3518. view()->Remove( &area ); // Stop drawing the selection shape
  3519. frame()->GetCanvas()->SetCurrentCursor( KICURSOR::ARROW ); // Reset cursor to default
  3520. if( !cancelled )
  3521. selectionTool->GetSelection().ClearReferencePoint();
  3522. m_toolMgr->ProcessEvent( EVENTS::UninhibitSelectionEditing );
  3523. return cancelled;
  3524. }
  3525. void PCB_LASSO_SELECTION_TOOL::setTransitions()
  3526. {
  3527. Go( &PCB_LASSO_SELECTION_TOOL::SelectPolyArea, ACTIONS::selectInsideLasso.MakeEvent() );
  3528. Go( &PCB_LASSO_SELECTION_TOOL::SelectPolyArea, ACTIONS::selectTouchingLasso.MakeEvent() );
  3529. Go( &PCB_LASSO_SELECTION_TOOL::SelectPolyArea, ACTIONS::selectAutoLasso.MakeEvent() );
  3530. Go( &PCB_LASSO_SELECTION_TOOL::SelectPolyArea, ACTIONS::selectTouchingPath.MakeEvent() );
  3531. }