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.

4212 lines
130 KiB

6 months ago
5 years ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
5 years ago
3 years ago
9 months ago
11 years ago
9 years ago
9 years ago
11 years ago
11 years ago
11 years ago
11 years ago
9 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 <preview_items/selection_area.h>
  56. #include <gal/painter.h>
  57. #include <router/router_tool.h>
  58. #include <pcbnew_settings.h>
  59. #include <tool/tool_event.h>
  60. #include <tool/tool_manager.h>
  61. #include <tools/tool_event_utils.h>
  62. #include <tools/pcb_point_editor.h>
  63. #include <tools/pcb_selection_tool.h>
  64. #include <tools/pcb_actions.h>
  65. #include <tools/board_inspection_tool.h>
  66. #include <connectivity/connectivity_data.h>
  67. #include <ratsnest/ratsnest_data.h>
  68. #include <footprint_viewer_frame.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. selectMultiple();
  382. }
  383. else if( m_selection.Empty() && dragAction != MOUSE_DRAG_ACTION::DRAG_ANY )
  384. {
  385. selectMultiple();
  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 selectMultiple()
  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. selectMultiple();
  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. bool PCB_SELECTION_TOOL::selectTableCells( PCB_TABLE* aTable )
  787. {
  788. bool cancelled = false; // Was the tool canceled while it was running?
  789. m_multiple = true; // Multiple selection mode is active
  790. for( PCB_TABLECELL* cell : aTable->GetCells() )
  791. {
  792. if( cell->IsSelected() )
  793. cell->SetFlags( CANDIDATE );
  794. else
  795. cell->ClearFlags( CANDIDATE );
  796. }
  797. auto wasSelected =
  798. []( EDA_ITEM* aItem )
  799. {
  800. return ( aItem->GetFlags() & CANDIDATE ) > 0;
  801. };
  802. while( TOOL_EVENT* evt = Wait() )
  803. {
  804. if( evt->IsCancelInteractive() || evt->IsActivate() )
  805. {
  806. cancelled = true;
  807. break;
  808. }
  809. else if( evt->IsDrag( BUT_LEFT ) )
  810. {
  811. getViewControls()->SetAutoPan( true );
  812. BOX2I selectionRect( evt->DragOrigin(), evt->Position() - evt->DragOrigin() );
  813. selectionRect.Normalize();
  814. for( PCB_TABLECELL* cell : aTable->GetCells() )
  815. {
  816. bool doSelect = false;
  817. if( cell->HitTest( selectionRect, false ) )
  818. {
  819. if( m_subtractive )
  820. doSelect = false;
  821. else if( m_exclusive_or )
  822. doSelect = !wasSelected( cell );
  823. else
  824. doSelect = true;
  825. }
  826. else if( wasSelected( cell ) )
  827. {
  828. doSelect = m_additive || m_subtractive || m_exclusive_or;
  829. }
  830. if( doSelect && !cell->IsSelected() )
  831. select( cell );
  832. else if( !doSelect && cell->IsSelected() )
  833. unselect( cell );
  834. }
  835. }
  836. else if( evt->IsMouseUp( BUT_LEFT ) )
  837. {
  838. m_selection.SetIsHover( false );
  839. bool anyAdded = false;
  840. bool anySubtracted = false;
  841. for( PCB_TABLECELL* cell : aTable->GetCells() )
  842. {
  843. if( cell->IsSelected() && !wasSelected( cell ) )
  844. anyAdded = true;
  845. else if( wasSelected( cell ) && !cell->IsSelected() )
  846. anySubtracted = true;
  847. }
  848. // Inform other potentially interested tools
  849. if( anyAdded )
  850. m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
  851. if( anySubtracted )
  852. m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent );
  853. break; // Stop waiting for events
  854. }
  855. else
  856. {
  857. // Allow some actions for navigation
  858. for( int i = 0; allowedActions[i]; ++i )
  859. {
  860. if( evt->IsAction( allowedActions[i] ) )
  861. {
  862. evt->SetPassEvent();
  863. break;
  864. }
  865. }
  866. }
  867. }
  868. getViewControls()->SetAutoPan( false );
  869. m_multiple = false; // Multiple selection mode is inactive
  870. if( !cancelled )
  871. m_selection.ClearReferencePoint();
  872. return cancelled;
  873. }
  874. bool PCB_SELECTION_TOOL::selectMultiple()
  875. {
  876. bool cancelled = false; // Was the tool canceled while it was running?
  877. m_multiple = true; // Multiple selection mode is active
  878. KIGFX::VIEW* view = getView();
  879. KIGFX::PREVIEW::SELECTION_AREA area;
  880. view->Add( &area );
  881. bool anyAdded = false;
  882. bool anySubtracted = false;
  883. while( TOOL_EVENT* evt = Wait() )
  884. {
  885. /* Selection mode depends on direction of drag-selection:
  886. * Left > Right : Select objects that are fully enclosed by selection
  887. * Right > Left : Select objects that are crossed by selection
  888. */
  889. bool greedySelection = area.GetEnd().x < area.GetOrigin().x;
  890. if( view->IsMirroredX() )
  891. greedySelection = !greedySelection;
  892. m_frame->GetCanvas()->SetCurrentCursor( !greedySelection ? KICURSOR::SELECT_WINDOW
  893. : KICURSOR::SELECT_LASSO );
  894. if( evt->IsCancelInteractive() || evt->IsActivate() )
  895. {
  896. cancelled = true;
  897. break;
  898. }
  899. if( evt->IsDrag( BUT_LEFT ) )
  900. {
  901. if( !m_drag_additive && !m_drag_subtractive )
  902. {
  903. if( m_selection.GetSize() > 0 )
  904. {
  905. anySubtracted = true;
  906. ClearSelection( true /*quiet mode*/ );
  907. }
  908. }
  909. // Start drawing a selection box
  910. area.SetOrigin( evt->DragOrigin() );
  911. area.SetEnd( evt->Position() );
  912. area.SetAdditive( m_drag_additive );
  913. area.SetSubtractive( m_drag_subtractive );
  914. area.SetExclusiveOr( false );
  915. view->SetVisible( &area, true );
  916. view->Update( &area );
  917. getViewControls()->SetAutoPan( true );
  918. }
  919. if( evt->IsMouseUp( BUT_LEFT ) )
  920. {
  921. getViewControls()->SetAutoPan( false );
  922. // End drawing the selection box
  923. view->SetVisible( &area, false );
  924. std::vector<KIGFX::VIEW::LAYER_ITEM_PAIR> candidates;
  925. BOX2I selectionRect = area.ViewBBox();
  926. view->Query( selectionRect, candidates ); // Get the list of nearby items
  927. selectionRect.Normalize();
  928. GENERAL_COLLECTOR collector;
  929. GENERAL_COLLECTOR padsCollector;
  930. std::set<EDA_ITEM*> group_items;
  931. for( PCB_GROUP* group : board()->Groups() )
  932. {
  933. // The currently entered group does not get limited
  934. if( m_enteredGroup == group )
  935. continue;
  936. std::unordered_set<EDA_ITEM*>& newset = group->GetItems();
  937. // If we are not greedy and have selected the whole group, add just one item
  938. // to allow it to be promoted to the group later
  939. if( !greedySelection && selectionRect.Contains( group->GetBoundingBox() )
  940. && newset.size() )
  941. {
  942. for( EDA_ITEM* group_item : newset )
  943. {
  944. if( !group_item->IsBOARD_ITEM() )
  945. continue;
  946. if( Selectable( static_cast<BOARD_ITEM*>( group_item ) ) )
  947. collector.Append( *newset.begin() );
  948. }
  949. }
  950. for( EDA_ITEM* group_item : newset )
  951. group_items.emplace( group_item );
  952. }
  953. for( const auto& [item, layer] : candidates )
  954. {
  955. if( !item->IsBOARD_ITEM() )
  956. continue;
  957. BOARD_ITEM* boardItem = static_cast<BOARD_ITEM*>( item );
  958. if( Selectable( boardItem ) && boardItem->HitTest( selectionRect, !greedySelection )
  959. && ( greedySelection || !group_items.count( boardItem ) ) )
  960. {
  961. if( boardItem->Type() == PCB_PAD_T && !m_isFootprintEditor )
  962. padsCollector.Append( boardItem );
  963. else
  964. collector.Append( boardItem );
  965. }
  966. }
  967. // Apply the stateful filter
  968. FilterCollectedItems( collector, true );
  969. FilterCollectorForHierarchy( collector, true );
  970. // If we selected nothing but pads, allow them to be selected
  971. if( collector.GetCount() == 0 )
  972. {
  973. collector = padsCollector;
  974. FilterCollectedItems( collector, true );
  975. FilterCollectorForHierarchy( collector, true );
  976. }
  977. // Sort the filtered selection by rows and columns to have a nice default
  978. // for tools that can use it.
  979. std::sort( collector.begin(), collector.end(),
  980. []( EDA_ITEM* a, EDA_ITEM* b )
  981. {
  982. VECTOR2I aPos = a->GetPosition();
  983. VECTOR2I bPos = b->GetPosition();
  984. if( aPos.y == bPos.y )
  985. return aPos.x < bPos.x;
  986. return aPos.y < bPos.y;
  987. } );
  988. for( EDA_ITEM* i : collector )
  989. {
  990. if( !i->IsBOARD_ITEM() )
  991. continue;
  992. BOARD_ITEM* item = static_cast<BOARD_ITEM*>( i );
  993. if( m_subtractive || ( m_exclusive_or && item->IsSelected() ) )
  994. {
  995. unselect( item );
  996. anySubtracted = true;
  997. }
  998. else
  999. {
  1000. select( item );
  1001. anyAdded = true;
  1002. }
  1003. }
  1004. m_selection.SetIsHover( false );
  1005. // Inform other potentially interested tools
  1006. if( anyAdded )
  1007. m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
  1008. else if( anySubtracted )
  1009. m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent );
  1010. break; // Stop waiting for events
  1011. }
  1012. // Allow some actions for navigation
  1013. for( int i = 0; allowedActions[i]; ++i )
  1014. {
  1015. if( evt->IsAction( allowedActions[i] ) )
  1016. {
  1017. evt->SetPassEvent();
  1018. break;
  1019. }
  1020. }
  1021. }
  1022. getViewControls()->SetAutoPan( false );
  1023. // Stop drawing the selection box
  1024. view->Remove( &area );
  1025. m_multiple = false; // Multiple selection mode is inactive
  1026. if( !cancelled )
  1027. m_selection.ClearReferencePoint();
  1028. m_toolMgr->ProcessEvent( EVENTS::UninhibitSelectionEditing );
  1029. return cancelled;
  1030. }
  1031. int PCB_SELECTION_TOOL::disambiguateCursor( const TOOL_EVENT& aEvent )
  1032. {
  1033. wxMouseState keyboardState = wxGetMouseState();
  1034. setModifiersState( keyboardState.ShiftDown(), keyboardState.ControlDown(),
  1035. keyboardState.AltDown() );
  1036. m_skip_heuristics = true;
  1037. selectPoint( m_originalCursor, false, &m_canceledMenu );
  1038. m_skip_heuristics = false;
  1039. return 0;
  1040. }
  1041. int PCB_SELECTION_TOOL::CursorSelection( const TOOL_EVENT& aEvent )
  1042. {
  1043. CLIENT_SELECTION_FILTER aClientFilter = aEvent.Parameter<CLIENT_SELECTION_FILTER>();
  1044. selectCursor( false, aClientFilter );
  1045. return 0;
  1046. }
  1047. int PCB_SELECTION_TOOL::ClearSelection( const TOOL_EVENT& aEvent )
  1048. {
  1049. ClearSelection();
  1050. return 0;
  1051. }
  1052. int PCB_SELECTION_TOOL::SelectAll( const TOOL_EVENT& aEvent )
  1053. {
  1054. GENERAL_COLLECTOR collection;
  1055. BOX2I selectionBox;
  1056. selectionBox.SetMaximum();
  1057. getView()->Query( selectionBox,
  1058. [&]( KIGFX::VIEW_ITEM* viewItem ) -> bool
  1059. {
  1060. if( viewItem->IsBOARD_ITEM() )
  1061. {
  1062. BOARD_ITEM* item = static_cast<BOARD_ITEM*>( viewItem );
  1063. if( item && Selectable( item ) && itemPassesFilter( item, true ) )
  1064. collection.Append( item );
  1065. }
  1066. return true;
  1067. } );
  1068. FilterCollectorForHierarchy( collection, true );
  1069. for( EDA_ITEM* item : collection )
  1070. select( item );
  1071. m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
  1072. m_frame->GetCanvas()->ForceRefresh();
  1073. return 0;
  1074. }
  1075. int PCB_SELECTION_TOOL::UnselectAll( const TOOL_EVENT& aEvent )
  1076. {
  1077. BOX2I selectionBox;
  1078. selectionBox.SetMaximum();
  1079. getView()->Query( selectionBox,
  1080. [&]( KIGFX::VIEW_ITEM* viewItem ) -> bool
  1081. {
  1082. if( viewItem->IsBOARD_ITEM() )
  1083. {
  1084. BOARD_ITEM* item = static_cast<BOARD_ITEM*>( viewItem );
  1085. if( item && Selectable( item ) )
  1086. unselect( item );
  1087. }
  1088. return true;
  1089. } );
  1090. m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent );
  1091. m_frame->GetCanvas()->ForceRefresh();
  1092. return 0;
  1093. }
  1094. void connectedItemFilter( const VECTOR2I&, GENERAL_COLLECTOR& aCollector,
  1095. PCB_SELECTION_TOOL* sTool )
  1096. {
  1097. // Narrow the collection down to a single BOARD_CONNECTED_ITEM for each represented net.
  1098. // All other items types are removed.
  1099. std::set<int> representedNets;
  1100. for( int i = aCollector.GetCount() - 1; i >= 0; i-- )
  1101. {
  1102. BOARD_CONNECTED_ITEM* item = dynamic_cast<BOARD_CONNECTED_ITEM*>( aCollector[i] );
  1103. if( !item )
  1104. aCollector.Remove( i );
  1105. else if ( representedNets.count( item->GetNetCode() ) )
  1106. aCollector.Remove( i );
  1107. else
  1108. representedNets.insert( item->GetNetCode() );
  1109. }
  1110. }
  1111. int PCB_SELECTION_TOOL::unrouteSelected( const TOOL_EVENT& aEvent )
  1112. {
  1113. std::deque<EDA_ITEM*> selectedItems = m_selection.GetItems();
  1114. // Get all footprints and pads
  1115. std::vector<BOARD_CONNECTED_ITEM*> toUnroute;
  1116. for( EDA_ITEM* item : selectedItems )
  1117. {
  1118. if( item->Type() == PCB_FOOTPRINT_T )
  1119. {
  1120. for( PAD* pad : static_cast<FOOTPRINT*>( item )->Pads() )
  1121. toUnroute.push_back( pad );
  1122. }
  1123. else if( BOARD_CONNECTED_ITEM::ClassOf( item ) )
  1124. {
  1125. toUnroute.push_back( static_cast<BOARD_CONNECTED_ITEM*>( item ) );
  1126. }
  1127. }
  1128. // Clear selection so we don't delete our footprints/pads
  1129. ClearSelection( true );
  1130. // Get the tracks on our list of pads, then delete them
  1131. selectAllConnectedTracks( toUnroute, STOP_CONDITION::STOP_AT_PAD );
  1132. m_toolMgr->RunAction( ACTIONS::doDelete );
  1133. // Reselect our footprint/pads as they were in our original selection
  1134. for( EDA_ITEM* item : selectedItems )
  1135. {
  1136. if( item->Type() == PCB_FOOTPRINT_T || item->Type() == PCB_PAD_T )
  1137. select( item );
  1138. }
  1139. return 0;
  1140. }
  1141. int PCB_SELECTION_TOOL::unrouteSegment( const TOOL_EVENT& aEvent )
  1142. {
  1143. std::deque<EDA_ITEM*> selectedItems = m_selection.GetItems();
  1144. // Get all footprints and pads
  1145. std::vector<BOARD_CONNECTED_ITEM*> toUnroute;
  1146. for( EDA_ITEM* item : selectedItems )
  1147. {
  1148. if( item->Type() == PCB_TRACE_T || item->Type() == PCB_ARC_T || item->Type() == PCB_VIA_T )
  1149. {
  1150. toUnroute.push_back( static_cast<BOARD_CONNECTED_ITEM*>( item ) );
  1151. }
  1152. }
  1153. // Get the tracks connecting to our starting objects
  1154. ClearSelection();
  1155. selectAllConnectedTracks( toUnroute, STOP_CONDITION::STOP_AT_SEGMENT );
  1156. std::deque<EDA_ITEM*> toSelectAfter;
  1157. // This will select the unroute items too, so filter them out
  1158. for( EDA_ITEM* item : m_selection.GetItemsSortedByTypeAndXY() )
  1159. {
  1160. if( !item->IsBOARD_ITEM() )
  1161. continue;
  1162. if( std::find( toUnroute.begin(), toUnroute.end(), item ) == toUnroute.end() )
  1163. toSelectAfter.push_back( item );
  1164. }
  1165. ClearSelection( true );
  1166. for( EDA_ITEM* item : toUnroute )
  1167. select( item );
  1168. m_toolMgr->RunAction( ACTIONS::doDelete );
  1169. // Now our after tracks so the user can continue backing up as desired
  1170. ClearSelection( true );
  1171. for( EDA_ITEM* item : toSelectAfter )
  1172. select( item );
  1173. return 0;
  1174. }
  1175. int PCB_SELECTION_TOOL::expandConnection( const TOOL_EVENT& aEvent )
  1176. {
  1177. // expandConnection will get called no matter whether the user selected a connected item or a
  1178. // non-connected shape (graphic on a non-copper layer). The algorithm for expanding to connected
  1179. // items is different from graphics, so they need to be handled separately.
  1180. unsigned initialCount = 0;
  1181. for( const EDA_ITEM* item : m_selection.GetItems() )
  1182. {
  1183. if( item->Type() == PCB_FOOTPRINT_T
  1184. || item->Type() == PCB_GENERATOR_T
  1185. || ( static_cast<const BOARD_ITEM*>( item )->IsConnected() ) )
  1186. {
  1187. initialCount++;
  1188. }
  1189. }
  1190. if( initialCount == 0 )
  1191. {
  1192. // First, process any graphic shapes we have
  1193. std::vector<PCB_SHAPE*> startShapes;
  1194. for( EDA_ITEM* item : m_selection.GetItems() )
  1195. {
  1196. if( isExpandableGraphicShape( item ) )
  1197. startShapes.push_back( static_cast<PCB_SHAPE*>( item ) );
  1198. }
  1199. // If no non-copper shapes; fall back to looking for connected items
  1200. if( !startShapes.empty() )
  1201. selectAllConnectedShapes( startShapes );
  1202. else
  1203. selectCursor( true, connectedItemFilter );
  1204. }
  1205. m_frame->SetStatusText( _( "Select/Expand Connection..." ) );
  1206. for( STOP_CONDITION stopCondition : { STOP_AT_JUNCTION, STOP_AT_PAD, STOP_NEVER } )
  1207. {
  1208. std::deque<EDA_ITEM*> selectedItems = m_selection.GetItems();
  1209. for( EDA_ITEM* item : selectedItems )
  1210. item->ClearTempFlags();
  1211. std::vector<BOARD_CONNECTED_ITEM*> startItems;
  1212. for( EDA_ITEM* item : selectedItems )
  1213. {
  1214. if( item->Type() == PCB_FOOTPRINT_T )
  1215. {
  1216. FOOTPRINT* footprint = static_cast<FOOTPRINT*>( item );
  1217. for( PAD* pad : footprint->Pads() )
  1218. startItems.push_back( pad );
  1219. }
  1220. else if( item->Type() == PCB_GENERATOR_T )
  1221. {
  1222. for( BOARD_ITEM* generatedItem : static_cast<PCB_GENERATOR*>( item )->GetBoardItems() )
  1223. {
  1224. if( BOARD_CONNECTED_ITEM::ClassOf( generatedItem ) )
  1225. startItems.push_back( static_cast<BOARD_CONNECTED_ITEM*>( generatedItem ) );
  1226. }
  1227. }
  1228. else if( BOARD_CONNECTED_ITEM::ClassOf( item ) )
  1229. {
  1230. startItems.push_back( static_cast<BOARD_CONNECTED_ITEM*>( item ) );
  1231. }
  1232. }
  1233. selectAllConnectedTracks( startItems, stopCondition );
  1234. if( m_selection.GetItems().size() > initialCount )
  1235. break;
  1236. }
  1237. m_frame->SetStatusText( wxEmptyString );
  1238. // Inform other potentially interested tools
  1239. m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
  1240. return 0;
  1241. }
  1242. void PCB_SELECTION_TOOL::selectAllConnectedTracks( const std::vector<BOARD_CONNECTED_ITEM*>& aStartItems,
  1243. STOP_CONDITION aStopCondition )
  1244. {
  1245. PROF_TIMER refreshTimer;
  1246. double refreshIntervalMs = 500; // Refresh display with this interval to indicate progress
  1247. int lastSelectionSize = (int) m_selection.GetSize();
  1248. auto connectivity = board()->GetConnectivity();
  1249. std::set<PAD*> startPadSet;
  1250. std::vector<BOARD_CONNECTED_ITEM*> cleanupItems;
  1251. for( BOARD_CONNECTED_ITEM* startItem : aStartItems )
  1252. {
  1253. // Register starting pads
  1254. if( startItem->Type() == PCB_PAD_T )
  1255. startPadSet.insert( static_cast<PAD*>( startItem ) );
  1256. // Select any starting track items
  1257. if( startItem->IsType( { PCB_TRACE_T, PCB_ARC_T, PCB_VIA_T } ) )
  1258. select( startItem );
  1259. }
  1260. for( BOARD_CONNECTED_ITEM* startItem : aStartItems )
  1261. {
  1262. std::map<VECTOR2I, std::vector<PCB_TRACK*>> trackMap;
  1263. std::map<VECTOR2I, PCB_VIA*> viaMap;
  1264. std::map<VECTOR2I, PAD*> padMap;
  1265. std::map<VECTOR2I, std::vector<PCB_SHAPE*>> shapeMap;
  1266. std::vector<std::pair<VECTOR2I, LSET>> activePts;
  1267. if( startItem->HasFlag( SKIP_STRUCT ) ) // Skip already visited items
  1268. continue;
  1269. auto connectedItems = connectivity->GetConnectedItems( startItem,
  1270. EXCLUDE_ZONES | IGNORE_NETS );
  1271. // Build maps of connected items
  1272. for( BOARD_CONNECTED_ITEM* item : connectedItems )
  1273. {
  1274. switch( item->Type() )
  1275. {
  1276. case PCB_ARC_T:
  1277. case PCB_TRACE_T:
  1278. {
  1279. PCB_TRACK* track = static_cast<PCB_TRACK*>( item );
  1280. trackMap[track->GetStart()].push_back( track );
  1281. trackMap[track->GetEnd()].push_back( track );
  1282. break;
  1283. }
  1284. case PCB_VIA_T:
  1285. {
  1286. PCB_VIA* via = static_cast<PCB_VIA*>( item );
  1287. viaMap[via->GetStart()] = via;
  1288. break;
  1289. }
  1290. case PCB_PAD_T:
  1291. {
  1292. PAD* pad = static_cast<PAD*>( item );
  1293. padMap[pad->GetPosition()] = pad;
  1294. break;
  1295. }
  1296. case PCB_SHAPE_T:
  1297. {
  1298. PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( item );
  1299. for( const auto& point : shape->GetConnectionPoints() )
  1300. shapeMap[point].push_back( shape );
  1301. break;
  1302. }
  1303. default:
  1304. break;
  1305. }
  1306. }
  1307. // Set up the initial active points
  1308. switch( startItem->Type() )
  1309. {
  1310. case PCB_ARC_T:
  1311. case PCB_TRACE_T:
  1312. {
  1313. PCB_TRACK* track = static_cast<PCB_TRACK*>( startItem );
  1314. activePts.push_back( { track->GetStart(), track->GetLayerSet() } );
  1315. activePts.push_back( { track->GetEnd(), track->GetLayerSet() } );
  1316. break;
  1317. }
  1318. case PCB_VIA_T:
  1319. activePts.push_back( { startItem->GetPosition(), startItem->GetLayerSet() } );
  1320. break;
  1321. case PCB_PAD_T:
  1322. activePts.push_back( { startItem->GetPosition(), startItem->GetLayerSet() } );
  1323. break;
  1324. case PCB_SHAPE_T:
  1325. {
  1326. PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( startItem );
  1327. for( const auto& point : shape->GetConnectionPoints() )
  1328. activePts.push_back( { point, startItem->GetLayerSet() } );
  1329. break;
  1330. }
  1331. default:
  1332. break;
  1333. }
  1334. bool expand = true;
  1335. int failSafe = 0;
  1336. // Iterative push from all active points
  1337. while( expand && failSafe++ < 100000 )
  1338. {
  1339. expand = false;
  1340. for( int i = (int) activePts.size() - 1; i >= 0; --i )
  1341. {
  1342. VECTOR2I pt = activePts[i].first;
  1343. LSET layerSetCu = activePts[i].second & LSET::AllCuMask();
  1344. auto viaIt = viaMap.find( pt );
  1345. auto padIt = padMap.find( pt );
  1346. bool gotVia = ( viaIt != viaMap.end() )
  1347. && ( layerSetCu & ( viaIt->second->GetLayerSet() ) ).any();
  1348. bool gotPad = ( padIt != padMap.end() )
  1349. && ( layerSetCu & ( padIt->second->GetLayerSet() ) ).any();
  1350. bool gotNonStartPad =
  1351. gotPad && ( startPadSet.find( padIt->second ) == startPadSet.end() );
  1352. if( aStopCondition == STOP_AT_JUNCTION )
  1353. {
  1354. size_t pt_count = 0;
  1355. for( PCB_TRACK* track : trackMap[pt] )
  1356. {
  1357. if( track->GetStart() != track->GetEnd()
  1358. && layerSetCu.Contains( track->GetLayer() ) )
  1359. {
  1360. pt_count++;
  1361. }
  1362. }
  1363. if( pt_count > 2 || gotVia || gotNonStartPad )
  1364. {
  1365. activePts.erase( activePts.begin() + i );
  1366. continue;
  1367. }
  1368. }
  1369. else if( aStopCondition == STOP_AT_PAD )
  1370. {
  1371. if( gotNonStartPad )
  1372. {
  1373. activePts.erase( activePts.begin() + i );
  1374. continue;
  1375. }
  1376. }
  1377. if( gotPad )
  1378. {
  1379. PAD* pad = padIt->second;
  1380. if( !pad->HasFlag( SKIP_STRUCT ) )
  1381. {
  1382. pad->SetFlags( SKIP_STRUCT );
  1383. cleanupItems.push_back( pad );
  1384. activePts.push_back( { pad->GetPosition(), pad->GetLayerSet() } );
  1385. expand = true;
  1386. }
  1387. }
  1388. for( PCB_TRACK* track : trackMap[pt] )
  1389. {
  1390. if( !layerSetCu.Contains( track->GetLayer() ) )
  1391. continue;
  1392. if( !track->IsSelected() )
  1393. select( track );
  1394. if( !track->HasFlag( SKIP_STRUCT ) )
  1395. {
  1396. track->SetFlags( SKIP_STRUCT );
  1397. cleanupItems.push_back( track );
  1398. if( track->GetStart() == pt )
  1399. activePts.push_back( { track->GetEnd(), track->GetLayerSet() } );
  1400. else
  1401. activePts.push_back( { track->GetStart(), track->GetLayerSet() } );
  1402. if( aStopCondition != STOP_AT_SEGMENT )
  1403. expand = true;
  1404. }
  1405. }
  1406. for( PCB_SHAPE* shape : shapeMap[pt] )
  1407. {
  1408. if( !layerSetCu.Contains( shape->GetLayer() ) )
  1409. continue;
  1410. if( !shape->IsSelected() )
  1411. select( shape );
  1412. if( !shape->HasFlag( SKIP_STRUCT ) )
  1413. {
  1414. shape->SetFlags( SKIP_STRUCT );
  1415. cleanupItems.push_back( shape );
  1416. for( const VECTOR2I& newPoint : shape->GetConnectionPoints() )
  1417. {
  1418. if( newPoint == pt )
  1419. continue;
  1420. activePts.push_back( { newPoint, shape->GetLayerSet() } );
  1421. }
  1422. if( aStopCondition != STOP_AT_SEGMENT )
  1423. expand = true;
  1424. }
  1425. }
  1426. if( viaMap.count( pt ) )
  1427. {
  1428. PCB_VIA* via = viaMap[pt];
  1429. if( !via->IsSelected() )
  1430. select( via );
  1431. if( !via->HasFlag( SKIP_STRUCT ) )
  1432. {
  1433. via->SetFlags( SKIP_STRUCT );
  1434. cleanupItems.push_back( via );
  1435. activePts.push_back( { via->GetPosition(), via->GetLayerSet() } );
  1436. if( aStopCondition != STOP_AT_SEGMENT )
  1437. expand = true;
  1438. }
  1439. }
  1440. activePts.erase( activePts.begin() + i );
  1441. }
  1442. // Refresh display for the feel of progress
  1443. if( refreshTimer.msecs() >= refreshIntervalMs )
  1444. {
  1445. if( m_selection.Size() != lastSelectionSize )
  1446. {
  1447. m_frame->GetCanvas()->ForceRefresh();
  1448. lastSelectionSize = m_selection.Size();
  1449. }
  1450. refreshTimer.Start();
  1451. }
  1452. }
  1453. }
  1454. std::set<EDA_ITEM*> toDeselect;
  1455. std::set<EDA_ITEM*> toSelect;
  1456. // Promote generated members to their PCB_GENERATOR parents
  1457. for( EDA_ITEM* item : m_selection )
  1458. {
  1459. if( !item->IsBOARD_ITEM() )
  1460. continue;
  1461. BOARD_ITEM* boardItem = static_cast<BOARD_ITEM*>( item );
  1462. EDA_GROUP* parent = boardItem->GetParentGroup();
  1463. if( parent && parent->AsEdaItem()->Type() == PCB_GENERATOR_T )
  1464. {
  1465. toDeselect.insert( item );
  1466. if( !parent->AsEdaItem()->IsSelected() )
  1467. toSelect.insert( parent->AsEdaItem() );
  1468. }
  1469. }
  1470. for( EDA_ITEM* item : toDeselect )
  1471. unselect( item );
  1472. for( EDA_ITEM* item : toSelect )
  1473. select( item );
  1474. for( BOARD_CONNECTED_ITEM* item : cleanupItems )
  1475. item->ClearFlags( SKIP_STRUCT );
  1476. }
  1477. bool PCB_SELECTION_TOOL::isExpandableGraphicShape( const EDA_ITEM* aItem ) const
  1478. {
  1479. if( aItem->Type() == PCB_SHAPE_T )
  1480. {
  1481. const PCB_SHAPE* shape = static_cast<const PCB_SHAPE*>( aItem );
  1482. switch( shape->GetShape() )
  1483. {
  1484. case SHAPE_T::SEGMENT:
  1485. case SHAPE_T::ARC:
  1486. case SHAPE_T::BEZIER:
  1487. return !shape->IsOnCopperLayer();
  1488. case SHAPE_T::POLY:
  1489. return !shape->IsOnCopperLayer() && !shape->IsClosed();
  1490. default:
  1491. return false;
  1492. }
  1493. }
  1494. return false;
  1495. }
  1496. void PCB_SELECTION_TOOL::selectAllConnectedShapes( const std::vector<PCB_SHAPE*>& aStartItems )
  1497. {
  1498. std::stack<PCB_SHAPE*> toSearch;
  1499. std::set<PCB_SHAPE*> toCleanup;
  1500. for( PCB_SHAPE* startItem : aStartItems )
  1501. toSearch.push( startItem );
  1502. GENERAL_COLLECTOR collector;
  1503. GENERAL_COLLECTORS_GUIDE guide = getCollectorsGuide();
  1504. auto searchPoint = [&]( const VECTOR2I& aWhere )
  1505. {
  1506. collector.Collect( board(), { PCB_SHAPE_T }, aWhere, guide );
  1507. for( EDA_ITEM* item : collector )
  1508. {
  1509. if( isExpandableGraphicShape( item ) )
  1510. toSearch.push( static_cast<PCB_SHAPE*>( item ) );
  1511. }
  1512. };
  1513. while( !toSearch.empty() )
  1514. {
  1515. PCB_SHAPE* shape = toSearch.top();
  1516. toSearch.pop();
  1517. if( shape->HasFlag( SKIP_STRUCT ) )
  1518. continue;
  1519. select( shape );
  1520. shape->SetFlags( SKIP_STRUCT );
  1521. toCleanup.insert( shape );
  1522. guide.SetLayerVisibleBits( shape->GetLayerSet() );
  1523. searchPoint( shape->GetStart() );
  1524. searchPoint( shape->GetEnd() );
  1525. }
  1526. for( PCB_SHAPE* shape : toCleanup )
  1527. shape->ClearFlags( SKIP_STRUCT );
  1528. }
  1529. int PCB_SELECTION_TOOL::selectUnconnected( const TOOL_EVENT& aEvent )
  1530. {
  1531. // Get all pads
  1532. std::vector<PAD*> pads;
  1533. for( EDA_ITEM* item : m_selection.GetItems() )
  1534. {
  1535. if( item->Type() == PCB_FOOTPRINT_T )
  1536. {
  1537. for( PAD* pad : static_cast<FOOTPRINT*>( item )->Pads() )
  1538. pads.push_back( pad );
  1539. }
  1540. else if( item->Type() == PCB_PAD_T )
  1541. {
  1542. pads.push_back( static_cast<PAD*>( item ) );
  1543. }
  1544. }
  1545. // Select every footprint on the end of the ratsnest for each pad in our selection
  1546. std::shared_ptr<CONNECTIVITY_DATA> conn = board()->GetConnectivity();
  1547. for( PAD* pad : pads )
  1548. {
  1549. for( const CN_EDGE& edge : conn->GetRatsnestForPad( pad ) )
  1550. {
  1551. wxCHECK2( edge.GetSourceNode() && !edge.GetSourceNode()->Dirty(), continue );
  1552. wxCHECK2( edge.GetTargetNode() && !edge.GetTargetNode()->Dirty(), continue );
  1553. BOARD_CONNECTED_ITEM* sourceParent = edge.GetSourceNode()->Parent();
  1554. BOARD_CONNECTED_ITEM* targetParent = edge.GetTargetNode()->Parent();
  1555. if( sourceParent == pad )
  1556. {
  1557. if( targetParent->Type() == PCB_PAD_T )
  1558. select( static_cast<PAD*>( targetParent )->GetParent() );
  1559. }
  1560. else if( targetParent == pad )
  1561. {
  1562. if( sourceParent->Type() == PCB_PAD_T )
  1563. select( static_cast<PAD*>( sourceParent )->GetParent() );
  1564. }
  1565. }
  1566. }
  1567. return 0;
  1568. }
  1569. int PCB_SELECTION_TOOL::grabUnconnected( const TOOL_EVENT& aEvent )
  1570. {
  1571. PCB_SELECTION originalSelection = m_selection;
  1572. // Get all pads
  1573. std::vector<PAD*> pads;
  1574. for( EDA_ITEM* item : m_selection.GetItems() )
  1575. {
  1576. if( item->Type() == PCB_FOOTPRINT_T )
  1577. {
  1578. for( PAD* pad : static_cast<FOOTPRINT*>( item )->Pads() )
  1579. pads.push_back( pad );
  1580. }
  1581. else if( item->Type() == PCB_PAD_T )
  1582. {
  1583. pads.push_back( static_cast<PAD*>( item ) );
  1584. }
  1585. }
  1586. ClearSelection();
  1587. // Select every footprint on the end of the ratsnest for each pad in our selection
  1588. std::shared_ptr<CONNECTIVITY_DATA> conn = board()->GetConnectivity();
  1589. for( PAD* pad : pads )
  1590. {
  1591. const std::vector<CN_EDGE> edges = conn->GetRatsnestForPad( pad );
  1592. // Need to have something unconnected to grab
  1593. if( edges.size() == 0 )
  1594. continue;
  1595. double currentDistance = DBL_MAX;
  1596. FOOTPRINT* nearest = nullptr;
  1597. // Check every ratsnest line for the nearest one
  1598. for( const CN_EDGE& edge : edges )
  1599. {
  1600. if( edge.GetSourceNode()->Parent()->GetParentFootprint()
  1601. == edge.GetTargetNode()->Parent()->GetParentFootprint() )
  1602. {
  1603. continue; // This edge is a loop on the same footprint
  1604. }
  1605. // Figure out if we are the source or the target node on the ratnest
  1606. const CN_ANCHOR* other = edge.GetSourceNode()->Parent() == pad ? edge.GetTargetNode().get()
  1607. : edge.GetSourceNode().get();
  1608. wxCHECK2( other && !other->Dirty(), continue );
  1609. // We only want to grab footprints, so the ratnest has to point to a pad
  1610. if( other->Parent()->Type() != PCB_PAD_T )
  1611. continue;
  1612. if( edge.GetLength() < currentDistance )
  1613. {
  1614. currentDistance = edge.GetLength();
  1615. nearest = other->Parent()->GetParentFootprint();
  1616. }
  1617. }
  1618. if( nearest != nullptr )
  1619. select( nearest );
  1620. }
  1621. m_toolMgr->RunAction( PCB_ACTIONS::moveIndividually );
  1622. return 0;
  1623. }
  1624. void PCB_SELECTION_TOOL::SelectAllItemsOnNet( int aNetCode, bool aSelect )
  1625. {
  1626. std::shared_ptr<CONNECTIVITY_DATA> conn = board()->GetConnectivity();
  1627. for( BOARD_ITEM* item : conn->GetNetItems( aNetCode, { PCB_TRACE_T,
  1628. PCB_ARC_T,
  1629. PCB_VIA_T,
  1630. PCB_SHAPE_T } ) )
  1631. {
  1632. if( itemPassesFilter( item, true ) )
  1633. aSelect ? select( item ) : unselect( item );
  1634. }
  1635. }
  1636. int PCB_SELECTION_TOOL::selectNet( const TOOL_EVENT& aEvent )
  1637. {
  1638. bool select = aEvent.IsAction( &PCB_ACTIONS::selectNet );
  1639. // If we've been passed an argument, just select that netcode1
  1640. int netcode = aEvent.Parameter<int>();
  1641. if( netcode > 0 )
  1642. {
  1643. SelectAllItemsOnNet( netcode, select );
  1644. // Inform other potentially interested tools
  1645. if( m_selection.Size() > 0 )
  1646. m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
  1647. else
  1648. m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent );
  1649. return 0;
  1650. }
  1651. if( !selectCursor() )
  1652. return 0;
  1653. // copy the selection, since we're going to iterate and modify
  1654. auto selection = m_selection.GetItems();
  1655. for( EDA_ITEM* i : selection )
  1656. {
  1657. BOARD_CONNECTED_ITEM* connItem = dynamic_cast<BOARD_CONNECTED_ITEM*>( i );
  1658. if( connItem )
  1659. SelectAllItemsOnNet( connItem->GetNetCode(), select );
  1660. }
  1661. // Inform other potentially interested tools
  1662. if( m_selection.Size() > 0 )
  1663. m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
  1664. else
  1665. m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent );
  1666. return 0;
  1667. }
  1668. void PCB_SELECTION_TOOL::selectAllItemsOnSheet( wxString& aSheetPath )
  1669. {
  1670. std::vector<BOARD_ITEM*> footprints;
  1671. // store all footprints that are on that sheet path
  1672. for( FOOTPRINT* footprint : board()->Footprints() )
  1673. {
  1674. if( footprint == nullptr )
  1675. continue;
  1676. wxString footprint_path = footprint->GetPath().AsString().BeforeLast( '/' );
  1677. if( footprint_path.IsEmpty() )
  1678. footprint_path += '/';
  1679. if( footprint_path == aSheetPath )
  1680. footprints.push_back( footprint );
  1681. }
  1682. for( BOARD_ITEM* i : footprints )
  1683. {
  1684. if( i != nullptr )
  1685. select( i );
  1686. }
  1687. selectConnections( footprints );
  1688. }
  1689. void PCB_SELECTION_TOOL::selectConnections( const std::vector<BOARD_ITEM*>& aItems )
  1690. {
  1691. // Generate a list of all pads, and of all nets they belong to.
  1692. std::list<int> netcodeList;
  1693. std::vector<BOARD_CONNECTED_ITEM*> padList;
  1694. for( BOARD_ITEM* item : aItems )
  1695. {
  1696. switch( item->Type() )
  1697. {
  1698. case PCB_FOOTPRINT_T:
  1699. {
  1700. for( PAD* pad : static_cast<FOOTPRINT*>( item )->Pads() )
  1701. {
  1702. if( pad->IsConnected() )
  1703. {
  1704. netcodeList.push_back( pad->GetNetCode() );
  1705. padList.push_back( pad );
  1706. }
  1707. }
  1708. break;
  1709. }
  1710. case PCB_PAD_T:
  1711. {
  1712. PAD* pad = static_cast<PAD*>( item );
  1713. if( pad->IsConnected() )
  1714. {
  1715. netcodeList.push_back( pad->GetNetCode() );
  1716. padList.push_back( pad );
  1717. }
  1718. break;
  1719. }
  1720. default:
  1721. break;
  1722. }
  1723. }
  1724. // Sort for binary search
  1725. std::sort( padList.begin(), padList.end() );
  1726. // remove all duplicates
  1727. netcodeList.sort();
  1728. netcodeList.unique();
  1729. selectAllConnectedTracks( padList, STOP_AT_PAD );
  1730. // now we need to find all footprints that are connected to each of these nets then we need
  1731. // to determine if these footprints are in the list of footprints
  1732. std::vector<int> removeCodeList;
  1733. std::shared_ptr<CONNECTIVITY_DATA> conn = board()->GetConnectivity();
  1734. for( int netCode : netcodeList )
  1735. {
  1736. for( BOARD_CONNECTED_ITEM* pad : conn->GetNetItems( netCode, { PCB_PAD_T } ) )
  1737. {
  1738. if( !std::binary_search( padList.begin(), padList.end(), pad ) )
  1739. {
  1740. // if we cannot find the pad in the padList then we can assume that that pad
  1741. // should not be used, therefore invalidate this netcode.
  1742. removeCodeList.push_back( netCode );
  1743. break;
  1744. }
  1745. }
  1746. }
  1747. for( int removeCode : removeCodeList )
  1748. netcodeList.remove( removeCode );
  1749. std::unordered_set<BOARD_ITEM*> localConnectionList;
  1750. for( int netCode : netcodeList )
  1751. {
  1752. for( BOARD_ITEM* item : conn->GetNetItems( netCode, { PCB_TRACE_T,
  1753. PCB_ARC_T,
  1754. PCB_VIA_T,
  1755. PCB_SHAPE_T } ) )
  1756. {
  1757. localConnectionList.insert( item );
  1758. }
  1759. }
  1760. for( BOARD_ITEM* item : localConnectionList )
  1761. select( item );
  1762. }
  1763. int PCB_SELECTION_TOOL::syncSelection( const TOOL_EVENT& aEvent )
  1764. {
  1765. std::vector<BOARD_ITEM*>* items = aEvent.Parameter<std::vector<BOARD_ITEM*>*>();
  1766. if( items )
  1767. doSyncSelection( *items, false );
  1768. return 0;
  1769. }
  1770. int PCB_SELECTION_TOOL::syncSelectionWithNets( const TOOL_EVENT& aEvent )
  1771. {
  1772. std::vector<BOARD_ITEM*>* items = aEvent.Parameter<std::vector<BOARD_ITEM*>*>();
  1773. if( items )
  1774. doSyncSelection( *items, true );
  1775. return 0;
  1776. }
  1777. void PCB_SELECTION_TOOL::doSyncSelection( const std::vector<BOARD_ITEM*>& aItems, bool aWithNets )
  1778. {
  1779. if( m_selection.Front() && m_selection.Front()->IsMoving() )
  1780. return;
  1781. ClearSelection( true /*quiet mode*/ );
  1782. // Perform individual selection of each item before processing the event.
  1783. for( BOARD_ITEM* item : aItems )
  1784. select( item );
  1785. if( aWithNets )
  1786. selectConnections( aItems );
  1787. BOX2I bbox = m_selection.GetBoundingBox();
  1788. if( bbox.GetWidth() != 0 && bbox.GetHeight() != 0 )
  1789. {
  1790. if( m_frame->GetPcbNewSettings()->m_CrossProbing.center_on_items )
  1791. {
  1792. if( m_frame->GetPcbNewSettings()->m_CrossProbing.zoom_to_fit )
  1793. ZoomFitCrossProbeBBox( bbox );
  1794. m_frame->FocusOnLocation( bbox.Centre() );
  1795. }
  1796. }
  1797. view()->UpdateAllLayersColor();
  1798. m_frame->GetCanvas()->ForceRefresh();
  1799. if( m_selection.Size() > 0 )
  1800. m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
  1801. }
  1802. int PCB_SELECTION_TOOL::selectSheetContents( const TOOL_EVENT& aEvent )
  1803. {
  1804. ClearSelection( true /*quiet mode*/ );
  1805. wxString sheetPath = *aEvent.Parameter<wxString*>();
  1806. selectAllItemsOnSheet( sheetPath );
  1807. zoomFitSelection();
  1808. if( m_selection.Size() > 0 )
  1809. m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
  1810. return 0;
  1811. }
  1812. int PCB_SELECTION_TOOL::selectSameSheet( const TOOL_EVENT& aEvent )
  1813. {
  1814. // this function currently only supports footprints since they are only on one sheet.
  1815. EDA_ITEM* item = m_selection.Front();
  1816. if( !item )
  1817. return 0;
  1818. if( item->Type() != PCB_FOOTPRINT_T )
  1819. return 0;
  1820. FOOTPRINT* footprint = dynamic_cast<FOOTPRINT*>( item );
  1821. if( !footprint || footprint->GetPath().empty() )
  1822. return 0;
  1823. ClearSelection( true /*quiet mode*/ );
  1824. // get the sheet path only.
  1825. wxString sheetPath = footprint->GetPath().AsString().BeforeLast( '/' );
  1826. if( sheetPath.IsEmpty() )
  1827. sheetPath += '/';
  1828. selectAllItemsOnSheet( sheetPath );
  1829. // Inform other potentially interested tools
  1830. if( m_selection.Size() > 0 )
  1831. m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
  1832. return 0;
  1833. }
  1834. void PCB_SELECTION_TOOL::zoomFitSelection()
  1835. {
  1836. // Should recalculate the view to zoom in on the selection.
  1837. BOX2I selectionBox = m_selection.GetBoundingBox();
  1838. KIGFX::VIEW* view = getView();
  1839. VECTOR2D screenSize = view->ToWorld( ToVECTOR2D( m_frame->GetCanvas()->GetClientSize() ),
  1840. false );
  1841. screenSize.x = std::max( 10.0, screenSize.x );
  1842. screenSize.y = std::max( 10.0, screenSize.y );
  1843. if( selectionBox.GetWidth() != 0 || selectionBox.GetHeight() != 0 )
  1844. {
  1845. VECTOR2D vsize = selectionBox.GetSize();
  1846. double scale = view->GetScale() / std::max( fabs( vsize.x / screenSize.x ),
  1847. fabs( vsize.y / screenSize.y ) );
  1848. view->SetScale( scale );
  1849. view->SetCenter( selectionBox.Centre() );
  1850. view->Add( &m_selection );
  1851. }
  1852. m_frame->GetCanvas()->ForceRefresh();
  1853. }
  1854. void PCB_SELECTION_TOOL::ZoomFitCrossProbeBBox( const BOX2I& aBBox )
  1855. {
  1856. // Should recalculate the view to zoom in on the bbox.
  1857. KIGFX::VIEW* view = getView();
  1858. if( aBBox.GetWidth() == 0 )
  1859. return;
  1860. BOX2I bbox = aBBox;
  1861. bbox.Normalize();
  1862. //#define DEFAULT_PCBNEW_CODE // Un-comment for normal full zoom KiCad algorithm
  1863. #ifdef DEFAULT_PCBNEW_CODE
  1864. auto bbSize = bbox.Inflate( bbox.GetWidth() * 0.2f ).GetSize();
  1865. auto screenSize = view->ToWorld( GetCanvas()->GetClientSize(), false );
  1866. // The "fabs" on x ensures the right answer when the view is flipped
  1867. screenSize.x = std::max( 10.0, fabs( screenSize.x ) );
  1868. screenSize.y = std::max( 10.0, screenSize.y );
  1869. double ratio = std::max( fabs( bbSize.x / screenSize.x ), fabs( bbSize.y / screenSize.y ) );
  1870. // Try not to zoom on every cross-probe; it gets very noisy
  1871. if( crossProbingSettings.zoom_to_fit && ( ratio < 0.5 || ratio > 1.0 ) )
  1872. view->SetScale( view->GetScale() / ratio );
  1873. #endif // DEFAULT_PCBNEW_CODE
  1874. #ifndef DEFAULT_PCBNEW_CODE // Do the scaled zoom
  1875. auto bbSize = bbox.Inflate( KiROUND( bbox.GetWidth() * 0.2 ) ).GetSize();
  1876. VECTOR2D screenSize = view->ToWorld( ToVECTOR2D( m_frame->GetCanvas()->GetClientSize() ),
  1877. false );
  1878. // This code tries to come up with a zoom factor that doesn't simply zoom in
  1879. // to the cross probed component, but instead shows a reasonable amount of the
  1880. // circuit around it to provide context. This reduces or eliminates the need
  1881. // to manually change the zoom because it's too close.
  1882. // Using the default text height as a constant to compare against, use the
  1883. // height of the bounding box of visible items for a footprint to figure out
  1884. // if this is a big footprint (like a processor) or a small footprint (like a resistor).
  1885. // This ratio is not useful by itself as a scaling factor. It must be "bent" to
  1886. // provide good scaling at varying component sizes. Bigger components need less
  1887. // scaling than small ones.
  1888. double currTextHeight = pcbIUScale.mmToIU( DEFAULT_TEXT_SIZE );
  1889. double compRatio = bbSize.y / currTextHeight; // Ratio of component to text height
  1890. // This will end up as the scaling factor we apply to "ratio".
  1891. double compRatioBent = 1.0;
  1892. // This is similar to the original KiCad code that scaled the zoom to make sure
  1893. // components were visible on screen. It's simply a ratio of screen size to
  1894. // component size, and its job is to zoom in to make the component fullscreen.
  1895. // Earlier in the code the component BBox is given a 20% margin to add some
  1896. // breathing room. We compare the height of this enlarged component bbox to the
  1897. // default text height. If a component will end up with the sides clipped, we
  1898. // adjust later to make sure it fits on screen.
  1899. //
  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( -1.0, fabs( bbSize.y / screenSize.y ) );
  1904. // Original KiCad code for how much to scale the zoom
  1905. double kicadRatio = std::max( fabs( bbSize.x / screenSize.x ),
  1906. fabs( bbSize.y / screenSize.y ) );
  1907. // LUT to scale zoom ratio to provide reasonable schematic context. Must work
  1908. // with footprints of varying sizes (e.g. 0402 package and 200 pin BGA).
  1909. // "first" is used as the input and "second" as the output
  1910. //
  1911. // "first" = compRatio (footprint height / default text height)
  1912. // "second" = Amount to scale ratio by
  1913. std::vector<std::pair<double, double>> lut {
  1914. { 1, 8 },
  1915. { 1.5, 5 },
  1916. { 3, 3 },
  1917. { 4.5, 2.5 },
  1918. { 8, 2.0 },
  1919. { 12, 1.7 },
  1920. { 16, 1.5 },
  1921. { 24, 1.3 },
  1922. { 32, 1.0 },
  1923. };
  1924. std::vector<std::pair<double, double>>::iterator it;
  1925. compRatioBent = lut.back().second; // Large component default
  1926. if( compRatio >= lut.front().first )
  1927. {
  1928. // Use LUT to do linear interpolation of "compRatio" within "first", then
  1929. // use that result to linearly interpolate "second" which gives the scaling
  1930. // factor needed.
  1931. for( it = lut.begin(); it < lut.end() - 1; it++ )
  1932. {
  1933. if( it->first <= compRatio && next( it )->first >= compRatio )
  1934. {
  1935. double diffx = compRatio - it->first;
  1936. double diffn = next( it )->first - it->first;
  1937. compRatioBent = it->second + ( next( it )->second - it->second ) * diffx / diffn;
  1938. break; // We have our interpolated value
  1939. }
  1940. }
  1941. }
  1942. else
  1943. {
  1944. compRatioBent = lut.front().second; // Small component default
  1945. }
  1946. // If the width of the part we're probing is bigger than what the screen width will be
  1947. // after the zoom, then punt and use the KiCad zoom algorithm since it guarantees the
  1948. // part's width will be encompassed within the screen. This will apply to parts that
  1949. // are much wider than they are tall.
  1950. if( bbSize.x > screenSize.x * ratio * compRatioBent )
  1951. {
  1952. // Use standard KiCad zoom algorithm for parts too wide to fit screen/
  1953. ratio = kicadRatio;
  1954. compRatioBent = 1.0; // Reset so we don't modify the "KiCad" ratio
  1955. wxLogTrace( "CROSS_PROBE_SCALE",
  1956. "Part TOO WIDE for screen. Using normal KiCad zoom ratio: %1.5f", ratio );
  1957. }
  1958. // Now that "compRatioBent" holds our final scaling factor we apply it to the original
  1959. // fullscreen zoom ratio to arrive at the final ratio itself.
  1960. ratio *= compRatioBent;
  1961. bool alwaysZoom = false; // DEBUG - allows us to minimize zooming or not
  1962. // Try not to zoom on every cross-probe; it gets very noisy
  1963. if( ( ratio < 0.5 || ratio > 1.0 ) || alwaysZoom )
  1964. view->SetScale( view->GetScale() / ratio );
  1965. #endif // ifndef DEFAULT_PCBNEW_CODE
  1966. }
  1967. void PCB_SELECTION_TOOL::FindItem( BOARD_ITEM* aItem )
  1968. {
  1969. bool cleared = false;
  1970. if( m_selection.GetSize() > 0 )
  1971. {
  1972. // Don't fire an event now; most of the time it will be redundant as we're about to
  1973. // fire a SelectedEvent.
  1974. cleared = true;
  1975. ClearSelection( true /*quiet mode*/ );
  1976. }
  1977. if( aItem )
  1978. {
  1979. switch( aItem->Type() )
  1980. {
  1981. case PCB_NETINFO_T:
  1982. {
  1983. int netCode = static_cast<NETINFO_ITEM*>( aItem )->GetNetCode();
  1984. if( netCode > 0 )
  1985. {
  1986. SelectAllItemsOnNet( netCode, true );
  1987. m_frame->FocusOnLocation( aItem->GetCenter() );
  1988. }
  1989. break;
  1990. }
  1991. default:
  1992. select( aItem );
  1993. m_frame->FocusOnLocation( aItem->GetPosition() );
  1994. }
  1995. // If the item has a bounding box, then zoom out if needed
  1996. if( aItem->GetBoundingBox().GetHeight() > 0 && aItem->GetBoundingBox().GetWidth() > 0 )
  1997. {
  1998. // This adds some margin
  1999. double marginFactor = 2;
  2000. KIGFX::PCB_VIEW* pcbView = canvas()->GetView();
  2001. BOX2D screenBox = pcbView->GetViewport();
  2002. VECTOR2D screenSize = screenBox.GetSize();
  2003. BOX2I screenRect = BOX2ISafe( screenBox.GetOrigin(), screenSize / marginFactor );
  2004. if( !screenRect.Contains( aItem->GetBoundingBox() ) )
  2005. {
  2006. double scaleX = screenSize.x /
  2007. static_cast<double>( aItem->GetBoundingBox().GetWidth() );
  2008. double scaleY = screenSize.y /
  2009. static_cast<double>( aItem->GetBoundingBox().GetHeight() );
  2010. scaleX /= marginFactor;
  2011. scaleY /= marginFactor;
  2012. double scale = scaleX > scaleY ? scaleY : scaleX;
  2013. if( scale < 1 ) // Don't zoom in, only zoom out
  2014. {
  2015. pcbView->SetScale( pcbView->GetScale() * ( scale ) );
  2016. //Let's refocus because there is an algorithm to avoid dialogs in there.
  2017. m_frame->FocusOnLocation( aItem->GetCenter() );
  2018. }
  2019. }
  2020. }
  2021. // Inform other potentially interested tools
  2022. m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
  2023. }
  2024. else if( cleared )
  2025. {
  2026. m_toolMgr->ProcessEvent( EVENTS::ClearedEvent );
  2027. }
  2028. m_frame->GetCanvas()->ForceRefresh();
  2029. }
  2030. /**
  2031. * Determine if an item is included by the filter specified.
  2032. *
  2033. * @return true if aItem should be selected by this filter (i..e not filtered out)
  2034. */
  2035. static bool itemIsIncludedByFilter( const BOARD_ITEM& aItem, const BOARD& aBoard,
  2036. const DIALOG_FILTER_SELECTION::OPTIONS& aFilterOptions )
  2037. {
  2038. switch( aItem.Type() )
  2039. {
  2040. case PCB_FOOTPRINT_T:
  2041. {
  2042. const FOOTPRINT& footprint = static_cast<const FOOTPRINT&>( aItem );
  2043. return aFilterOptions.includeModules
  2044. && ( aFilterOptions.includeLockedModules || !footprint.IsLocked() );
  2045. }
  2046. case PCB_TRACE_T:
  2047. case PCB_ARC_T:
  2048. return aFilterOptions.includeTracks;
  2049. case PCB_VIA_T:
  2050. return aFilterOptions.includeVias;
  2051. case PCB_ZONE_T:
  2052. return aFilterOptions.includeZones;
  2053. case PCB_SHAPE_T:
  2054. case PCB_TARGET_T:
  2055. case PCB_DIM_ALIGNED_T:
  2056. case PCB_DIM_CENTER_T:
  2057. case PCB_DIM_RADIAL_T:
  2058. case PCB_DIM_ORTHOGONAL_T:
  2059. case PCB_DIM_LEADER_T:
  2060. if( aItem.GetLayer() == Edge_Cuts )
  2061. return aFilterOptions.includeBoardOutlineLayer;
  2062. else
  2063. return aFilterOptions.includeItemsOnTechLayers;
  2064. case PCB_FIELD_T:
  2065. case PCB_TEXT_T:
  2066. case PCB_TEXTBOX_T:
  2067. case PCB_TABLE_T:
  2068. case PCB_TABLECELL_T:
  2069. return aFilterOptions.includePcbTexts;
  2070. default:
  2071. // Filter dialog is inclusive, not exclusive. If it's not included, then it doesn't
  2072. // get selected.
  2073. return false;
  2074. }
  2075. }
  2076. int PCB_SELECTION_TOOL::filterSelection( const TOOL_EVENT& aEvent )
  2077. {
  2078. const BOARD& board = *getModel<BOARD>();
  2079. DIALOG_FILTER_SELECTION::OPTIONS& opts = m_priv->m_filterOpts;
  2080. DIALOG_FILTER_SELECTION dlg( m_frame, opts );
  2081. const int cmd = dlg.ShowModal();
  2082. if( cmd != wxID_OK )
  2083. return 0;
  2084. // copy current selection
  2085. std::deque<EDA_ITEM*> selection = m_selection.GetItems();
  2086. ClearSelection( true /*quiet mode*/ );
  2087. // re-select items from the saved selection according to the dialog options
  2088. for( EDA_ITEM* i : selection )
  2089. {
  2090. if( !i->IsBOARD_ITEM() )
  2091. continue;
  2092. BOARD_ITEM* item = static_cast<BOARD_ITEM*>( i );
  2093. bool include = itemIsIncludedByFilter( *item, board, opts );
  2094. if( include )
  2095. select( item );
  2096. }
  2097. m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
  2098. return 0;
  2099. }
  2100. void PCB_SELECTION_TOOL::FilterCollectedItems( GENERAL_COLLECTOR& aCollector, bool aMultiSelect )
  2101. {
  2102. if( aCollector.GetCount() == 0 )
  2103. return;
  2104. std::set<BOARD_ITEM*> rejected;
  2105. for( EDA_ITEM* i : aCollector )
  2106. {
  2107. if( !i->IsBOARD_ITEM() )
  2108. continue;
  2109. BOARD_ITEM* item = static_cast<BOARD_ITEM*>( i );
  2110. if( !itemPassesFilter( item, aMultiSelect ) )
  2111. rejected.insert( item );
  2112. }
  2113. for( BOARD_ITEM* item : rejected )
  2114. aCollector.Remove( item );
  2115. }
  2116. bool PCB_SELECTION_TOOL::itemPassesFilter( BOARD_ITEM* aItem, bool aMultiSelect )
  2117. {
  2118. if( !m_filter.lockedItems )
  2119. {
  2120. if( aItem->IsLocked() || ( aItem->GetParent() && aItem->GetParent()->IsLocked() ) )
  2121. {
  2122. if( aItem->Type() == PCB_PAD_T && !aMultiSelect )
  2123. {
  2124. // allow a single pad to be selected -- there are a lot of operations that
  2125. // require this so we allow this one inconsistency
  2126. }
  2127. else
  2128. {
  2129. return false;
  2130. }
  2131. }
  2132. }
  2133. if( !aItem )
  2134. return false;
  2135. KICAD_T itemType = aItem->Type();
  2136. if( itemType == PCB_GENERATOR_T )
  2137. {
  2138. if( static_cast<PCB_GENERATOR*>( aItem )->GetItems().empty() )
  2139. {
  2140. if( !m_filter.otherItems )
  2141. return false;
  2142. }
  2143. else
  2144. {
  2145. itemType = ( *static_cast<PCB_GENERATOR*>( aItem )->GetItems().begin() )->Type();
  2146. }
  2147. }
  2148. switch( itemType )
  2149. {
  2150. case PCB_FOOTPRINT_T:
  2151. if( !m_filter.footprints )
  2152. return false;
  2153. break;
  2154. case PCB_PAD_T:
  2155. if( !m_filter.pads )
  2156. return false;
  2157. break;
  2158. case PCB_TRACE_T:
  2159. case PCB_ARC_T:
  2160. if( !m_filter.tracks )
  2161. return false;
  2162. break;
  2163. case PCB_VIA_T:
  2164. if( !m_filter.vias )
  2165. return false;
  2166. break;
  2167. case PCB_ZONE_T:
  2168. {
  2169. ZONE* zone = static_cast<ZONE*>( aItem );
  2170. if( ( !m_filter.zones && !zone->GetIsRuleArea() )
  2171. || ( !m_filter.keepouts && zone->GetIsRuleArea() ) )
  2172. {
  2173. return false;
  2174. }
  2175. // m_SolderMaskBridges zone is a special zone, only used to showsolder mask briges
  2176. // after running DRC. it is not really a board item.
  2177. // Never select it or delete by a Commit.
  2178. if( zone == m_frame->GetBoard()->m_SolderMaskBridges )
  2179. return false;
  2180. break;
  2181. }
  2182. case PCB_SHAPE_T:
  2183. case PCB_TARGET_T:
  2184. if( !m_filter.graphics )
  2185. return false;
  2186. break;
  2187. case PCB_REFERENCE_IMAGE_T:
  2188. if( !m_filter.graphics )
  2189. return false;
  2190. // a reference image living in a footprint must not be selected inside the board editor
  2191. if( !m_isFootprintEditor && aItem->GetParentFootprint() )
  2192. return false;
  2193. break;
  2194. case PCB_FIELD_T:
  2195. case PCB_TEXT_T:
  2196. case PCB_TEXTBOX_T:
  2197. case PCB_TABLE_T:
  2198. case PCB_TABLECELL_T:
  2199. if( !m_filter.text )
  2200. return false;
  2201. break;
  2202. case PCB_DIM_ALIGNED_T:
  2203. case PCB_DIM_CENTER_T:
  2204. case PCB_DIM_RADIAL_T:
  2205. case PCB_DIM_ORTHOGONAL_T:
  2206. case PCB_DIM_LEADER_T:
  2207. if( !m_filter.dimensions )
  2208. return false;
  2209. break;
  2210. default:
  2211. if( !m_filter.otherItems )
  2212. return false;
  2213. }
  2214. return true;
  2215. }
  2216. void PCB_SELECTION_TOOL::ClearSelection( bool aQuietMode )
  2217. {
  2218. if( m_selection.Empty() )
  2219. return;
  2220. while( m_selection.GetSize() )
  2221. unhighlight( m_selection.Front(), SELECTED, &m_selection );
  2222. view()->Update( &m_selection );
  2223. m_selection.SetIsHover( false );
  2224. m_selection.ClearReferencePoint();
  2225. // Inform other potentially interested tools
  2226. if( !aQuietMode )
  2227. {
  2228. m_toolMgr->ProcessEvent( EVENTS::ClearedEvent );
  2229. m_toolMgr->RunAction( PCB_ACTIONS::hideLocalRatsnest );
  2230. }
  2231. }
  2232. void PCB_SELECTION_TOOL::RebuildSelection()
  2233. {
  2234. m_selection.Clear();
  2235. bool enteredGroupFound = false;
  2236. INSPECTOR_FUNC inspector =
  2237. [&]( EDA_ITEM* item, void* testData )
  2238. {
  2239. if( item->IsSelected() )
  2240. {
  2241. EDA_ITEM* parent = item->GetParent();
  2242. // Let selected parents handle their children.
  2243. if( parent && parent->IsSelected() )
  2244. return INSPECT_RESULT::CONTINUE;
  2245. highlight( item, SELECTED, &m_selection );
  2246. }
  2247. if( item->Type() == PCB_GROUP_T )
  2248. {
  2249. if( item == m_enteredGroup )
  2250. {
  2251. item->SetFlags( ENTERED );
  2252. enteredGroupFound = true;
  2253. }
  2254. else
  2255. {
  2256. item->ClearFlags( ENTERED );
  2257. }
  2258. }
  2259. return INSPECT_RESULT::CONTINUE;
  2260. };
  2261. board()->Visit( inspector, nullptr, m_isFootprintEditor ? GENERAL_COLLECTOR::FootprintItems
  2262. : GENERAL_COLLECTOR::AllBoardItems );
  2263. if( !enteredGroupFound )
  2264. {
  2265. m_enteredGroupOverlay.Clear();
  2266. m_enteredGroup = nullptr;
  2267. }
  2268. }
  2269. bool PCB_SELECTION_TOOL::Selectable( const BOARD_ITEM* aItem, bool checkVisibilityOnly ) const
  2270. {
  2271. const RENDER_SETTINGS* settings = getView()->GetPainter()->GetSettings();
  2272. const PCB_DISPLAY_OPTIONS& options = frame()->GetDisplayOptions();
  2273. auto visibleLayers =
  2274. [&]() -> LSET
  2275. {
  2276. if( m_isFootprintEditor )
  2277. {
  2278. LSET set;
  2279. for( PCB_LAYER_ID layer : LSET::AllLayersMask() )
  2280. set.set( layer, view()->IsLayerVisible( layer ) );
  2281. return set;
  2282. }
  2283. else
  2284. {
  2285. return board()->GetVisibleLayers();
  2286. }
  2287. };
  2288. auto layerVisible =
  2289. [&]( PCB_LAYER_ID aLayer )
  2290. {
  2291. if( m_isFootprintEditor )
  2292. return view()->IsLayerVisible( aLayer );
  2293. else
  2294. return board()->IsLayerVisible( aLayer );
  2295. };
  2296. if( settings->GetHighContrast() )
  2297. {
  2298. const std::set<int> activeLayers = settings->GetHighContrastLayers();
  2299. bool onActiveLayer = false;
  2300. for( int layer : activeLayers )
  2301. {
  2302. // NOTE: Only checking the regular layers (not GAL meta-layers)
  2303. if( layer < PCB_LAYER_ID_COUNT && aItem->IsOnLayer( ToLAYER_ID( layer ) ) )
  2304. {
  2305. onActiveLayer = true;
  2306. break;
  2307. }
  2308. }
  2309. if( !onActiveLayer && aItem->Type() != PCB_MARKER_T )
  2310. {
  2311. // We do not want to select items that are in the background
  2312. return false;
  2313. }
  2314. }
  2315. if( aItem->Type() == PCB_FOOTPRINT_T )
  2316. {
  2317. const FOOTPRINT* footprint = static_cast<const FOOTPRINT*>( aItem );
  2318. // In footprint editor, we do not want to select the footprint itself.
  2319. if( m_isFootprintEditor )
  2320. return false;
  2321. // Allow selection of footprints if some part of the footprint is visible.
  2322. if( footprint->GetSide() != UNDEFINED_LAYER && !m_skip_heuristics )
  2323. {
  2324. LSET boardSide = footprint->IsFlipped() ? LSET::BackMask() : LSET::FrontMask();
  2325. if( !( visibleLayers() & boardSide ).any() )
  2326. return false;
  2327. }
  2328. // If the footprint has no items except the reference and value fields, include the
  2329. // footprint in the selections.
  2330. if( footprint->GraphicalItems().empty()
  2331. && footprint->Pads().empty()
  2332. && footprint->Zones().empty() )
  2333. {
  2334. return true;
  2335. }
  2336. for( const BOARD_ITEM* item : footprint->GraphicalItems() )
  2337. {
  2338. if( Selectable( item, true ) )
  2339. return true;
  2340. }
  2341. for( const PAD* pad : footprint->Pads() )
  2342. {
  2343. if( Selectable( pad, true ) )
  2344. return true;
  2345. }
  2346. for( const ZONE* zone : footprint->Zones() )
  2347. {
  2348. if( Selectable( zone, true ) )
  2349. return true;
  2350. }
  2351. return false;
  2352. }
  2353. else if( aItem->Type() == PCB_GROUP_T )
  2354. {
  2355. PCB_GROUP* group = const_cast<PCB_GROUP*>( static_cast<const PCB_GROUP*>( aItem ) );
  2356. // Similar to logic for footprint, a group is selectable if any of its members are.
  2357. // (This recurses.)
  2358. for( BOARD_ITEM* item : group->GetBoardItems() )
  2359. {
  2360. if( Selectable( item, true ) )
  2361. return true;
  2362. }
  2363. return false;
  2364. }
  2365. if( aItem->GetParentGroup() && aItem->GetParentGroup()->AsEdaItem()->Type() == PCB_GENERATOR_T )
  2366. return false;
  2367. const ZONE* zone = nullptr;
  2368. const PCB_VIA* via = nullptr;
  2369. const PAD* pad = nullptr;
  2370. const PCB_TEXT* text = nullptr;
  2371. const PCB_FIELD* field = nullptr;
  2372. const PCB_MARKER* marker = nullptr;
  2373. // Most footprint children can only be selected in the footprint editor.
  2374. if( aItem->GetParentFootprint() && !m_isFootprintEditor && !checkVisibilityOnly )
  2375. {
  2376. if( aItem->Type() != PCB_FIELD_T && aItem->Type() != PCB_PAD_T
  2377. && aItem->Type() != PCB_TEXT_T )
  2378. {
  2379. return false;
  2380. }
  2381. }
  2382. switch( aItem->Type() )
  2383. {
  2384. case PCB_ZONE_T:
  2385. if( !board()->IsElementVisible( LAYER_ZONES ) || ( options.m_ZoneOpacity == 0.00 ) )
  2386. return false;
  2387. zone = static_cast<const ZONE*>( aItem );
  2388. // A teardrop is modelled as a property of a via, pad or the board (for track-to-track
  2389. // teardrops). The underlying zone is only an implementation detail.
  2390. if( zone->IsTeardropArea() && !board()->LegacyTeardrops() )
  2391. return false;
  2392. // zones can exist on multiple layers!
  2393. if( !( zone->GetLayerSet() & visibleLayers() ).any() )
  2394. return false;
  2395. break;
  2396. case PCB_TRACE_T:
  2397. case PCB_ARC_T:
  2398. if( !board()->IsElementVisible( LAYER_TRACKS ) || ( options.m_TrackOpacity == 0.00 ) )
  2399. return false;
  2400. if( !layerVisible( aItem->GetLayer() ) )
  2401. return false;
  2402. break;
  2403. case PCB_VIA_T:
  2404. if( !board()->IsElementVisible( LAYER_VIAS ) || ( options.m_ViaOpacity == 0.00 ) )
  2405. return false;
  2406. via = static_cast<const PCB_VIA*>( aItem );
  2407. // For vias it is enough if only one of its layers is visible
  2408. if( !( visibleLayers() & via->GetLayerSet() ).any() )
  2409. return false;
  2410. break;
  2411. case PCB_FIELD_T:
  2412. field = static_cast<const PCB_FIELD*>( aItem );
  2413. if( !field->IsVisible() )
  2414. return false;
  2415. if( field->IsReference() && !view()->IsLayerVisible( LAYER_FP_REFERENCES ) )
  2416. return false;
  2417. if( field->IsValue() && !view()->IsLayerVisible( LAYER_FP_VALUES ) )
  2418. return false;
  2419. // Handle all other fields with normal text visibility controls
  2420. KI_FALLTHROUGH;
  2421. case PCB_TEXT_T:
  2422. text = static_cast<const PCB_TEXT*>( aItem );
  2423. if( !layerVisible( text->GetLayer() ) )
  2424. return false;
  2425. // Apply the LOD visibility test as well
  2426. if( !view()->IsVisible( text ) )
  2427. return false;
  2428. if( aItem->GetParentFootprint() )
  2429. {
  2430. int controlLayer = LAYER_FP_TEXT;
  2431. if( text->GetText() == wxT( "${REFERENCE}" ) )
  2432. controlLayer = LAYER_FP_REFERENCES;
  2433. else if( text->GetText() == wxT( "${VALUE}" ) )
  2434. controlLayer = LAYER_FP_VALUES;
  2435. if( !view()->IsLayerVisible( controlLayer ) )
  2436. return false;
  2437. }
  2438. break;
  2439. case PCB_REFERENCE_IMAGE_T:
  2440. if( options.m_ImageOpacity == 0.00 )
  2441. return false;
  2442. // Bitmap images on board are hidden if LAYER_DRAW_BITMAPS is not visible
  2443. if( !view()->IsLayerVisible( LAYER_DRAW_BITMAPS ) )
  2444. return false;
  2445. KI_FALLTHROUGH;
  2446. case PCB_SHAPE_T:
  2447. if( options.m_FilledShapeOpacity == 0.0 && static_cast<const PCB_SHAPE*>( aItem )->IsAnyFill() )
  2448. return false;
  2449. KI_FALLTHROUGH;
  2450. case PCB_TEXTBOX_T:
  2451. case PCB_TABLE_T:
  2452. case PCB_TABLECELL_T:
  2453. if( !layerVisible( aItem->GetLayer() ) )
  2454. return false;
  2455. if( aItem->Type() == PCB_TABLECELL_T )
  2456. {
  2457. const PCB_TABLECELL* cell = static_cast<const PCB_TABLECELL*>( aItem );
  2458. if( cell->GetRowSpan() == 0 || cell->GetColSpan() == 0 )
  2459. return false;
  2460. }
  2461. break;
  2462. case PCB_DIM_ALIGNED_T:
  2463. case PCB_DIM_LEADER_T:
  2464. case PCB_DIM_CENTER_T:
  2465. case PCB_DIM_RADIAL_T:
  2466. case PCB_DIM_ORTHOGONAL_T:
  2467. if( !layerVisible( aItem->GetLayer() ) )
  2468. return false;
  2469. break;
  2470. case PCB_PAD_T:
  2471. if( options.m_PadOpacity == 0.00 )
  2472. return false;
  2473. pad = static_cast<const PAD*>( aItem );
  2474. if( pad->GetAttribute() == PAD_ATTRIB::PTH || pad->GetAttribute() == PAD_ATTRIB::NPTH )
  2475. {
  2476. // A pad's hole is visible on every layer the pad is visible on plus many layers the
  2477. // pad is not visible on -- so we only need to check for any visible hole layers.
  2478. if( !( visibleLayers() & LSET::PhysicalLayersMask() ).any() )
  2479. return false;
  2480. }
  2481. else
  2482. {
  2483. if( !( pad->GetLayerSet() & visibleLayers() ).any() )
  2484. return false;
  2485. }
  2486. break;
  2487. case PCB_MARKER_T:
  2488. marker = static_cast<const PCB_MARKER*>( aItem );
  2489. if( marker && marker->IsExcluded() && !board()->IsElementVisible( LAYER_DRC_EXCLUSION ) )
  2490. return false;
  2491. break;
  2492. // These are not selectable
  2493. case PCB_NETINFO_T:
  2494. case NOT_USED:
  2495. case TYPE_NOT_INIT:
  2496. return false;
  2497. default: // Suppress warnings
  2498. break;
  2499. }
  2500. return true;
  2501. }
  2502. void PCB_SELECTION_TOOL::select( EDA_ITEM* aItem )
  2503. {
  2504. if( !aItem || aItem->IsSelected() || !aItem->IsBOARD_ITEM() )
  2505. return;
  2506. if( aItem->Type() == PCB_PAD_T )
  2507. {
  2508. FOOTPRINT* footprint = static_cast<FOOTPRINT*>( aItem->GetParent() );
  2509. if( m_selection.Contains( footprint ) )
  2510. return;
  2511. }
  2512. if( m_enteredGroup &&
  2513. !PCB_GROUP::WithinScope( static_cast<BOARD_ITEM*>( aItem ), m_enteredGroup,
  2514. m_isFootprintEditor ) )
  2515. {
  2516. ExitGroup();
  2517. }
  2518. highlight( aItem, SELECTED, &m_selection );
  2519. }
  2520. void PCB_SELECTION_TOOL::unselect( EDA_ITEM* aItem )
  2521. {
  2522. unhighlight( aItem, SELECTED, &m_selection );
  2523. }
  2524. void PCB_SELECTION_TOOL::highlight( EDA_ITEM* aItem, int aMode, SELECTION* aGroup )
  2525. {
  2526. if( aGroup )
  2527. aGroup->Add( aItem );
  2528. highlightInternal( aItem, aMode, aGroup != nullptr );
  2529. view()->Update( aItem, KIGFX::REPAINT );
  2530. // Many selections are very temporal and updating the display each time just
  2531. // creates noise.
  2532. if( aMode == BRIGHTENED )
  2533. getView()->MarkTargetDirty( KIGFX::TARGET_OVERLAY );
  2534. }
  2535. void PCB_SELECTION_TOOL::highlightInternal( EDA_ITEM* aItem, int aMode, bool aUsingOverlay )
  2536. {
  2537. if( aMode == SELECTED )
  2538. aItem->SetSelected();
  2539. else if( aMode == BRIGHTENED )
  2540. aItem->SetBrightened();
  2541. if( aUsingOverlay && aMode != BRIGHTENED )
  2542. view()->Hide( aItem, true ); // Hide the original item, so it is shown only on overlay
  2543. if( aItem->IsBOARD_ITEM() )
  2544. {
  2545. BOARD_ITEM* boardItem = static_cast<BOARD_ITEM*>( aItem );
  2546. boardItem->RunOnChildren( std::bind( &PCB_SELECTION_TOOL::highlightInternal, this, _1, aMode, aUsingOverlay ),
  2547. RECURSE_MODE::RECURSE );
  2548. }
  2549. }
  2550. void PCB_SELECTION_TOOL::unhighlight( EDA_ITEM* aItem, int aMode, SELECTION* aGroup )
  2551. {
  2552. if( aGroup )
  2553. aGroup->Remove( aItem );
  2554. unhighlightInternal( aItem, aMode, aGroup != nullptr );
  2555. view()->Update( aItem, KIGFX::REPAINT );
  2556. // Many selections are very temporal and updating the display each time just creates noise.
  2557. if( aMode == BRIGHTENED )
  2558. getView()->MarkTargetDirty( KIGFX::TARGET_OVERLAY );
  2559. }
  2560. void PCB_SELECTION_TOOL::unhighlightInternal( EDA_ITEM* aItem, int aMode, bool aUsingOverlay )
  2561. {
  2562. if( aMode == SELECTED )
  2563. aItem->ClearSelected();
  2564. else if( aMode == BRIGHTENED )
  2565. aItem->ClearBrightened();
  2566. if( aUsingOverlay && aMode != BRIGHTENED )
  2567. {
  2568. view()->Hide( aItem, false ); // Restore original item visibility...
  2569. view()->Update( aItem ); // ... and make sure it's redrawn un-selected
  2570. }
  2571. if( aItem->IsBOARD_ITEM() )
  2572. {
  2573. BOARD_ITEM* boardItem = static_cast<BOARD_ITEM*>( aItem );
  2574. boardItem->RunOnChildren( std::bind( &PCB_SELECTION_TOOL::unhighlightInternal, this, _1, aMode, aUsingOverlay ),
  2575. RECURSE_MODE::RECURSE );
  2576. }
  2577. }
  2578. bool PCB_SELECTION_TOOL::selectionContains( const VECTOR2I& aPoint ) const
  2579. {
  2580. const unsigned GRIP_MARGIN = 20;
  2581. int margin = KiROUND( getView()->ToWorld( GRIP_MARGIN ) );
  2582. // Check if the point is located close to any of the currently selected items
  2583. for( EDA_ITEM* item : m_selection )
  2584. {
  2585. if( !item->IsBOARD_ITEM() )
  2586. continue;
  2587. BOX2I itemBox = item->ViewBBox();
  2588. itemBox.Inflate( margin ); // Give some margin for gripping an item
  2589. if( itemBox.Contains( aPoint ) )
  2590. {
  2591. if( item->HitTest( aPoint, margin ) )
  2592. return true;
  2593. bool found = false;
  2594. if( PCB_GROUP* group = dynamic_cast<PCB_GROUP*>( item ) )
  2595. {
  2596. group->RunOnChildren(
  2597. [&]( BOARD_ITEM* aItem )
  2598. {
  2599. if( aItem->HitTest( aPoint, margin ) )
  2600. found = true;
  2601. },
  2602. RECURSE_MODE::RECURSE );
  2603. }
  2604. if( found )
  2605. return true;
  2606. }
  2607. }
  2608. return false;
  2609. }
  2610. int PCB_SELECTION_TOOL::hitTestDistance( const VECTOR2I& aWhere, BOARD_ITEM* aItem,
  2611. int aMaxDistance ) const
  2612. {
  2613. BOX2D viewportD = getView()->GetViewport();
  2614. BOX2I viewport = BOX2ISafe( viewportD );
  2615. int distance = INT_MAX;
  2616. SEG loc( aWhere, aWhere );
  2617. switch( aItem->Type() )
  2618. {
  2619. case PCB_FIELD_T:
  2620. case PCB_TEXT_T:
  2621. {
  2622. PCB_TEXT* text = static_cast<PCB_TEXT*>( aItem );
  2623. // Add a bit of slop to text-shapes
  2624. if( text->GetEffectiveTextShape()->Collide( loc, aMaxDistance, &distance ) )
  2625. distance = std::clamp( distance - ( aMaxDistance / 2 ), 0, distance );
  2626. break;
  2627. }
  2628. case PCB_TEXTBOX_T:
  2629. case PCB_TABLECELL_T:
  2630. {
  2631. PCB_TEXTBOX* textbox = static_cast<PCB_TEXTBOX*>( aItem );
  2632. // Add a bit of slop to text-shapes
  2633. if( textbox->GetEffectiveTextShape()->Collide( loc, aMaxDistance, &distance ) )
  2634. distance = std::clamp( distance - ( aMaxDistance / 2 ), 0, distance );
  2635. break;
  2636. }
  2637. case PCB_TABLE_T:
  2638. {
  2639. PCB_TABLE* table = static_cast<PCB_TABLE*>( aItem );
  2640. for( PCB_TABLECELL* cell : table->GetCells() )
  2641. {
  2642. // Add a bit of slop to text-shapes
  2643. if( cell->GetEffectiveTextShape()->Collide( loc, aMaxDistance, &distance ) )
  2644. distance = std::clamp( distance - ( aMaxDistance / 2 ), 0, distance );
  2645. }
  2646. break;
  2647. }
  2648. case PCB_ZONE_T:
  2649. {
  2650. ZONE* zone = static_cast<ZONE*>( aItem );
  2651. // Zone borders are very specific
  2652. if( zone->HitTestForEdge( aWhere, aMaxDistance / 2 ) )
  2653. distance = 0;
  2654. else if( zone->HitTestForEdge( aWhere, aMaxDistance ) )
  2655. distance = aMaxDistance / 2;
  2656. else
  2657. aItem->GetEffectiveShape()->Collide( loc, aMaxDistance, &distance );
  2658. break;
  2659. }
  2660. case PCB_FOOTPRINT_T:
  2661. {
  2662. FOOTPRINT* footprint = static_cast<FOOTPRINT*>( aItem );
  2663. BOX2I bbox = footprint->GetBoundingBox( false );
  2664. try
  2665. {
  2666. footprint->GetBoundingHull().Collide( loc, aMaxDistance, &distance );
  2667. }
  2668. catch( const std::exception& e )
  2669. {
  2670. wxFAIL_MSG( wxString::Format( wxT( "Clipper exception occurred: %s" ), e.what() ) );
  2671. }
  2672. // Consider footprints larger than the viewport only as a last resort
  2673. if( bbox.GetHeight() > viewport.GetHeight() || bbox.GetWidth() > viewport.GetWidth() )
  2674. distance = INT_MAX / 2;
  2675. break;
  2676. }
  2677. case PCB_MARKER_T:
  2678. {
  2679. PCB_MARKER* marker = static_cast<PCB_MARKER*>( aItem );
  2680. SHAPE_LINE_CHAIN polygon;
  2681. marker->ShapeToPolygon( polygon );
  2682. polygon.Move( marker->GetPos() );
  2683. polygon.Collide( loc, aMaxDistance, &distance );
  2684. break;
  2685. }
  2686. case PCB_GROUP_T:
  2687. case PCB_GENERATOR_T:
  2688. {
  2689. PCB_GROUP* group = static_cast<PCB_GROUP*>( aItem );
  2690. for( BOARD_ITEM* member : group->GetBoardItems() )
  2691. distance = std::min( distance, hitTestDistance( aWhere, member, aMaxDistance ) );
  2692. break;
  2693. }
  2694. case PCB_PAD_T:
  2695. {
  2696. static_cast<PAD*>( aItem )->Padstack().ForEachUniqueLayer(
  2697. [&]( PCB_LAYER_ID aLayer )
  2698. {
  2699. int layerDistance = INT_MAX;
  2700. aItem->GetEffectiveShape( aLayer )->Collide( loc, aMaxDistance, &layerDistance );
  2701. distance = std::min( distance, layerDistance );
  2702. } );
  2703. break;
  2704. }
  2705. default:
  2706. aItem->GetEffectiveShape()->Collide( loc, aMaxDistance, &distance );
  2707. break;
  2708. }
  2709. return distance;
  2710. }
  2711. void PCB_SELECTION_TOOL::pruneObscuredSelectionCandidates( GENERAL_COLLECTOR& aCollector ) const
  2712. {
  2713. wxCHECK( m_frame, /* void */ );
  2714. if( aCollector.GetCount() < 2 )
  2715. return;
  2716. const RENDER_SETTINGS* settings = getView()->GetPainter()->GetSettings();
  2717. wxCHECK( settings, /* void */ );
  2718. PCB_LAYER_ID activeLayer = m_frame->GetActiveLayer();
  2719. LSET visibleLayers = m_frame->GetBoard()->GetVisibleLayers();
  2720. LSET enabledLayers = m_frame->GetBoard()->GetEnabledLayers();
  2721. LSEQ enabledLayerStack = enabledLayers.SeqStackupTop2Bottom( activeLayer );
  2722. wxCHECK( !enabledLayerStack.empty(), /* void */ );
  2723. auto isZoneFillKeepout =
  2724. []( const BOARD_ITEM* aItem ) -> bool
  2725. {
  2726. if( aItem->Type() == PCB_ZONE_T )
  2727. {
  2728. const ZONE* zone = static_cast<const ZONE*>( aItem );
  2729. if( zone->GetIsRuleArea() && zone->GetDoNotAllowZoneFills() )
  2730. return true;
  2731. }
  2732. return false;
  2733. };
  2734. std::vector<LAYER_OPACITY_ITEM> opacityStackup;
  2735. for( int i = 0; i < aCollector.GetCount(); i++ )
  2736. {
  2737. const BOARD_ITEM* item = aCollector[i];
  2738. LSET itemLayers = item->GetLayerSet() & enabledLayers & visibleLayers;
  2739. LSEQ itemLayerSeq = itemLayers.Seq( enabledLayerStack );
  2740. for( PCB_LAYER_ID layer : itemLayerSeq )
  2741. {
  2742. COLOR4D color = settings->GetColor( item, layer );
  2743. if( color.a == 0 )
  2744. continue;
  2745. LAYER_OPACITY_ITEM opacityItem;
  2746. opacityItem.m_Layer = layer;
  2747. opacityItem.m_Opacity = color.a;
  2748. opacityItem.m_Item = item;
  2749. if( isZoneFillKeepout( item ) )
  2750. opacityItem.m_Opacity = 0.0;
  2751. opacityStackup.emplace_back( opacityItem );
  2752. }
  2753. }
  2754. std::sort( opacityStackup.begin(), opacityStackup.end(),
  2755. [&]( const LAYER_OPACITY_ITEM& aLhs, const LAYER_OPACITY_ITEM& aRhs ) -> bool
  2756. {
  2757. int retv = enabledLayerStack.TestLayers( aLhs.m_Layer, aRhs.m_Layer );
  2758. if( retv )
  2759. return retv > 0;
  2760. return aLhs.m_Opacity > aRhs.m_Opacity;
  2761. } );
  2762. std::set<const BOARD_ITEM*> visibleItems;
  2763. std::set<const BOARD_ITEM*> itemsToRemove;
  2764. double minAlphaLimit = ADVANCED_CFG::GetCfg().m_PcbSelectionVisibilityRatio;
  2765. double currentStackupOpacity = 0.0;
  2766. PCB_LAYER_ID lastVisibleLayer = PCB_LAYER_ID::UNDEFINED_LAYER;
  2767. for( const LAYER_OPACITY_ITEM& opacityItem : opacityStackup )
  2768. {
  2769. if( lastVisibleLayer == PCB_LAYER_ID::UNDEFINED_LAYER )
  2770. {
  2771. currentStackupOpacity = opacityItem.m_Opacity;
  2772. lastVisibleLayer = opacityItem.m_Layer;
  2773. visibleItems.emplace( opacityItem.m_Item );
  2774. continue;
  2775. }
  2776. // Objects to ignore and fallback to the old selection behavior.
  2777. auto ignoreItem =
  2778. [&]()
  2779. {
  2780. const BOARD_ITEM* item = opacityItem.m_Item;
  2781. wxCHECK( item, false );
  2782. // Check items that span multiple layers for visibility.
  2783. if( visibleItems.count( item ) )
  2784. return true;
  2785. // Don't prune child items of a footprint that is already visible.
  2786. if( item->GetParent()
  2787. && ( item->GetParent()->Type() == PCB_FOOTPRINT_T )
  2788. && visibleItems.count( item->GetParent() ) )
  2789. {
  2790. return true;
  2791. }
  2792. // Keepout zones are transparent but for some reason, PCB_PAINTER::GetColor()
  2793. // returns the color of the zone it prevents from filling.
  2794. if( isZoneFillKeepout( item ) )
  2795. return true;
  2796. return false;
  2797. };
  2798. // Everything on the currently selected layer is visible;
  2799. if( opacityItem.m_Layer == enabledLayerStack[0] )
  2800. {
  2801. visibleItems.emplace( opacityItem.m_Item );
  2802. }
  2803. else
  2804. {
  2805. double itemVisibility = opacityItem.m_Opacity * ( 1.0 - currentStackupOpacity );
  2806. if( ( itemVisibility <= minAlphaLimit ) && !ignoreItem() )
  2807. itemsToRemove.emplace( opacityItem.m_Item );
  2808. else
  2809. visibleItems.emplace( opacityItem.m_Item );
  2810. }
  2811. if( opacityItem.m_Layer != lastVisibleLayer )
  2812. {
  2813. currentStackupOpacity += opacityItem.m_Opacity * ( 1.0 - currentStackupOpacity );
  2814. currentStackupOpacity = std::min( currentStackupOpacity, 1.0 );
  2815. lastVisibleLayer = opacityItem.m_Layer;
  2816. }
  2817. }
  2818. for( const BOARD_ITEM* itemToRemove : itemsToRemove )
  2819. {
  2820. wxCHECK( aCollector.GetCount() > 1, /* void */ );
  2821. aCollector.Remove( itemToRemove );
  2822. }
  2823. }
  2824. // The general idea here is that if the user clicks directly on a small item inside a larger
  2825. // one, then they want the small item. The quintessential case of this is clicking on a pad
  2826. // within a footprint, but we also apply it for text within a footprint, footprints within
  2827. // larger footprints, and vias within either larger pads or longer tracks.
  2828. //
  2829. // These "guesses" presume there is area within the larger item to click in to select it. If
  2830. // an item is mostly covered by smaller items within it, then the guesses are inappropriate as
  2831. // there might not be any area left to click to select the larger item. In this case we must
  2832. // leave the items in the collector and bring up a Selection Clarification menu.
  2833. //
  2834. // We currently check for pads and text mostly covering a footprint, but we don't check for
  2835. // smaller footprints mostly covering a larger footprint.
  2836. //
  2837. void PCB_SELECTION_TOOL::GuessSelectionCandidates( GENERAL_COLLECTOR& aCollector,
  2838. const VECTOR2I& aWhere ) const
  2839. {
  2840. static const LSET silkLayers( { B_SilkS, F_SilkS } );
  2841. static const LSET courtyardLayers( { B_CrtYd, F_CrtYd } );
  2842. static std::vector<KICAD_T> singleLayerSilkTypes = { PCB_FIELD_T,
  2843. PCB_TEXT_T, PCB_TEXTBOX_T,
  2844. PCB_TABLE_T, PCB_TABLECELL_T,
  2845. PCB_SHAPE_T };
  2846. if( ADVANCED_CFG::GetCfg().m_PcbSelectionVisibilityRatio != 1.0 )
  2847. pruneObscuredSelectionCandidates( aCollector );
  2848. if( aCollector.GetCount() == 1 )
  2849. return;
  2850. std::set<BOARD_ITEM*> preferred;
  2851. std::set<BOARD_ITEM*> rejected;
  2852. VECTOR2I where( aWhere.x, aWhere.y );
  2853. const RENDER_SETTINGS* settings = getView()->GetPainter()->GetSettings();
  2854. PCB_LAYER_ID activeLayer = m_frame->GetActiveLayer();
  2855. // If a silk layer is in front, we assume the user is working with silk and give preferential
  2856. // treatment to single-layer items on *either* silk layer.
  2857. if( silkLayers[activeLayer] )
  2858. {
  2859. for( int i = 0; i < aCollector.GetCount(); ++i )
  2860. {
  2861. BOARD_ITEM* item = aCollector[i];
  2862. if( item->IsType( singleLayerSilkTypes ) && silkLayers[ item->GetLayer() ] )
  2863. preferred.insert( item );
  2864. }
  2865. }
  2866. // Similarly, if a courtyard layer is in front, we assume the user is positioning footprints
  2867. // and give preferential treatment to footprints on *both* top and bottom.
  2868. else if( courtyardLayers[activeLayer] && settings->GetHighContrast() )
  2869. {
  2870. for( int i = 0; i < aCollector.GetCount(); ++i )
  2871. {
  2872. BOARD_ITEM* item = aCollector[i];
  2873. if( item->Type() == PCB_FOOTPRINT_T )
  2874. preferred.insert( item );
  2875. }
  2876. }
  2877. if( preferred.size() > 0 )
  2878. {
  2879. aCollector.Empty();
  2880. for( BOARD_ITEM* item : preferred )
  2881. aCollector.Append( item );
  2882. if( preferred.size() == 1 )
  2883. return;
  2884. }
  2885. // Prefer exact hits to sloppy ones
  2886. constexpr int MAX_SLOP = 5;
  2887. int singlePixel = KiROUND( aCollector.GetGuide()->OnePixelInIU() );
  2888. int maxSlop = KiROUND( MAX_SLOP * aCollector.GetGuide()->OnePixelInIU() );
  2889. int minSlop = INT_MAX;
  2890. std::map<BOARD_ITEM*, int> itemsBySloppiness;
  2891. for( int i = 0; i < aCollector.GetCount(); ++i )
  2892. {
  2893. BOARD_ITEM* item = aCollector[i];
  2894. int itemSlop = hitTestDistance( where, item, maxSlop );
  2895. itemsBySloppiness[ item ] = itemSlop;
  2896. if( itemSlop < minSlop )
  2897. minSlop = itemSlop;
  2898. }
  2899. // Prune sloppier items
  2900. if( minSlop < INT_MAX )
  2901. {
  2902. for( std::pair<BOARD_ITEM*, int> pair : itemsBySloppiness )
  2903. {
  2904. if( pair.second > minSlop + singlePixel )
  2905. aCollector.Transfer( pair.first );
  2906. }
  2907. }
  2908. // If the user clicked on a small item within a much larger one then it's pretty clear
  2909. // they're trying to select the smaller one.
  2910. constexpr double sizeRatio = 1.5;
  2911. std::vector<std::pair<BOARD_ITEM*, double>> itemsByArea;
  2912. for( int i = 0; i < aCollector.GetCount(); ++i )
  2913. {
  2914. BOARD_ITEM* item = aCollector[i];
  2915. double area = 0.0;
  2916. if( item->Type() == PCB_ZONE_T
  2917. && static_cast<ZONE*>( item )->HitTestForEdge( where, maxSlop / 2 ) )
  2918. {
  2919. // Zone borders are very specific, so make them "small"
  2920. area = (double) SEG::Square( singlePixel ) * MAX_SLOP;
  2921. }
  2922. else if( item->Type() == PCB_VIA_T )
  2923. {
  2924. // Vias rarely hide other things, and we don't want them deferring to short track
  2925. // segments underneath them -- so artificially reduce their size from πr² to r².
  2926. area = (double) SEG::Square( static_cast<PCB_VIA*>( item )->GetDrill() / 2 );
  2927. }
  2928. else if( item->Type() == PCB_REFERENCE_IMAGE_T )
  2929. {
  2930. BOX2I box = item->GetBoundingBox();
  2931. area = (double) box.GetWidth() * box.GetHeight();
  2932. }
  2933. else
  2934. {
  2935. try
  2936. {
  2937. area = FOOTPRINT::GetCoverageArea( item, aCollector );
  2938. }
  2939. catch( const std::exception& e )
  2940. {
  2941. wxFAIL_MSG( wxString::Format( wxT( "Clipper exception occurred: %s" ), e.what() ) );
  2942. }
  2943. }
  2944. itemsByArea.emplace_back( item, area );
  2945. }
  2946. std::sort( itemsByArea.begin(), itemsByArea.end(),
  2947. []( const std::pair<BOARD_ITEM*, double>& lhs,
  2948. const std::pair<BOARD_ITEM*, double>& rhs ) -> bool
  2949. {
  2950. return lhs.second < rhs.second;
  2951. } );
  2952. bool rejecting = false;
  2953. for( int i = 1; i < (int) itemsByArea.size(); ++i )
  2954. {
  2955. if( itemsByArea[i].second > itemsByArea[i-1].second * sizeRatio )
  2956. rejecting = true;
  2957. if( rejecting )
  2958. rejected.insert( itemsByArea[i].first );
  2959. }
  2960. // Special case: if a footprint is completely covered with other features then there's no
  2961. // way to select it -- so we need to leave it in the list for user disambiguation.
  2962. constexpr double maxCoverRatio = 0.70;
  2963. for( int i = 0; i < aCollector.GetCount(); ++i )
  2964. {
  2965. if( FOOTPRINT* footprint = dynamic_cast<FOOTPRINT*>( aCollector[i] ) )
  2966. {
  2967. if( footprint->CoverageRatio( aCollector ) > maxCoverRatio )
  2968. rejected.erase( footprint );
  2969. }
  2970. }
  2971. // Hopefully we've now got what the user wanted.
  2972. if( (unsigned) aCollector.GetCount() > rejected.size() ) // do not remove everything
  2973. {
  2974. for( BOARD_ITEM* item : rejected )
  2975. aCollector.Transfer( item );
  2976. }
  2977. // Finally, what we are left with is a set of items of similar coverage area. We now reject
  2978. // any that are not on the active layer, to reduce the number of disambiguation menus shown.
  2979. // If the user wants to force-disambiguate, they can either switch layers or use the modifier
  2980. // key to force the menu.
  2981. if( aCollector.GetCount() > 1 )
  2982. {
  2983. bool haveItemOnActive = false;
  2984. rejected.clear();
  2985. for( int i = 0; i < aCollector.GetCount(); ++i )
  2986. {
  2987. if( !aCollector[i]->IsOnLayer( activeLayer ) )
  2988. rejected.insert( aCollector[i] );
  2989. else
  2990. haveItemOnActive = true;
  2991. }
  2992. if( haveItemOnActive )
  2993. {
  2994. for( BOARD_ITEM* item : rejected )
  2995. aCollector.Transfer( item );
  2996. }
  2997. }
  2998. }
  2999. void PCB_SELECTION_TOOL::FilterCollectorForHierarchy( GENERAL_COLLECTOR& aCollector,
  3000. bool aMultiselect ) const
  3001. {
  3002. std::unordered_set<EDA_ITEM*> toAdd;
  3003. // Set CANDIDATE on all parents which are included in the GENERAL_COLLECTOR. This
  3004. // algorithm is O(3n), whereas checking for the parent inclusion could potentially be O(n^2).
  3005. for( int j = 0; j < aCollector.GetCount(); j++ )
  3006. {
  3007. if( aCollector[j]->GetParent() )
  3008. aCollector[j]->GetParent()->ClearFlags( CANDIDATE );
  3009. if( aCollector[j]->GetParentFootprint() )
  3010. aCollector[j]->GetParentFootprint()->ClearFlags( CANDIDATE );
  3011. }
  3012. if( aMultiselect )
  3013. {
  3014. for( int j = 0; j < aCollector.GetCount(); j++ )
  3015. aCollector[j]->SetFlags( CANDIDATE );
  3016. }
  3017. for( int j = 0; j < aCollector.GetCount(); )
  3018. {
  3019. BOARD_ITEM* item = aCollector[j];
  3020. FOOTPRINT* fp = item->GetParentFootprint();
  3021. BOARD_ITEM* start = item;
  3022. if( !m_isFootprintEditor && fp )
  3023. start = fp;
  3024. // If a group is entered, disallow selections of objects outside the group.
  3025. if( m_enteredGroup && !PCB_GROUP::WithinScope( item, m_enteredGroup, m_isFootprintEditor ) )
  3026. {
  3027. aCollector.Remove( item );
  3028. continue;
  3029. }
  3030. // If any element is a member of a group, replace those elements with the top containing
  3031. // group.
  3032. if( EDA_GROUP* top = PCB_GROUP::TopLevelGroup( start, m_enteredGroup, m_isFootprintEditor ) )
  3033. {
  3034. if( top->AsEdaItem() != item )
  3035. {
  3036. toAdd.insert( top->AsEdaItem() );
  3037. top->AsEdaItem()->SetFlags( CANDIDATE );
  3038. aCollector.Remove( item );
  3039. continue;
  3040. }
  3041. }
  3042. // Footprints are a bit easier as they can't be nested.
  3043. if( fp && ( fp->GetFlags() & CANDIDATE ) )
  3044. {
  3045. // Remove children of selected items
  3046. aCollector.Remove( item );
  3047. continue;
  3048. }
  3049. ++j;
  3050. }
  3051. for( EDA_ITEM* item : toAdd )
  3052. {
  3053. if( !aCollector.HasItem( item ) )
  3054. aCollector.Append( item );
  3055. }
  3056. }
  3057. void PCB_SELECTION_TOOL::FilterCollectorForTableCells( GENERAL_COLLECTOR& aCollector ) const
  3058. {
  3059. std::set<BOARD_ITEM*> to_add;
  3060. // Iterate from the back so we don't have to worry about removals.
  3061. for( int i = (int) aCollector.GetCount() - 1; i >= 0; --i )
  3062. {
  3063. BOARD_ITEM* item = aCollector[i];
  3064. if( item->Type() == PCB_TABLECELL_T )
  3065. {
  3066. if( !aCollector.HasItem( item->GetParent() ) )
  3067. to_add.insert( item->GetParent() );
  3068. aCollector.Remove( item );
  3069. }
  3070. }
  3071. for( BOARD_ITEM* item : to_add )
  3072. aCollector.Append( item );
  3073. }
  3074. void PCB_SELECTION_TOOL::FilterCollectorForFreePads( GENERAL_COLLECTOR& aCollector,
  3075. bool aForcePromotion ) const
  3076. {
  3077. std::set<BOARD_ITEM*> to_add;
  3078. // Iterate from the back so we don't have to worry about removals.
  3079. for( int i = aCollector.GetCount() - 1; i >= 0; --i )
  3080. {
  3081. BOARD_ITEM* item = aCollector[i];
  3082. if( !m_isFootprintEditor && item->Type() == PCB_PAD_T
  3083. && ( !frame()->GetPcbNewSettings()->m_AllowFreePads || aForcePromotion ) )
  3084. {
  3085. if( !aCollector.HasItem( item->GetParent() ) )
  3086. to_add.insert( item->GetParent() );
  3087. aCollector.Remove( item );
  3088. }
  3089. }
  3090. for( BOARD_ITEM* item : to_add )
  3091. aCollector.Append( item );
  3092. }
  3093. void PCB_SELECTION_TOOL::FilterCollectorForMarkers( GENERAL_COLLECTOR& aCollector ) const
  3094. {
  3095. // Iterate from the back so we don't have to worry about removals.
  3096. for( int i = aCollector.GetCount() - 1; i >= 0; --i )
  3097. {
  3098. BOARD_ITEM* item = aCollector[i];
  3099. if( item->Type() == PCB_MARKER_T )
  3100. aCollector.Remove( item );
  3101. }
  3102. }
  3103. void PCB_SELECTION_TOOL::FilterCollectorForFootprints( GENERAL_COLLECTOR& aCollector,
  3104. const VECTOR2I& aWhere ) const
  3105. {
  3106. const RENDER_SETTINGS* settings = getView()->GetPainter()->GetSettings();
  3107. BOX2D viewport = getView()->GetViewport();
  3108. BOX2I extents = BOX2ISafe( viewport );
  3109. bool need_direct_hit = false;
  3110. FOOTPRINT* single_fp = nullptr;
  3111. // If the designer is not modifying the existing selection AND we already have
  3112. // a selection, then we only want to select items that are directly under the cursor.
  3113. // This prevents us from being unable to clear the selection when zoomed into a footprint
  3114. if( !m_additive && !m_subtractive && !m_exclusive_or && m_selection.GetSize() > 0 )
  3115. {
  3116. need_direct_hit = true;
  3117. for( EDA_ITEM* item : m_selection )
  3118. {
  3119. FOOTPRINT* fp = nullptr;
  3120. if( item->Type() == PCB_FOOTPRINT_T )
  3121. fp = static_cast<FOOTPRINT*>( item );
  3122. else if( item->IsBOARD_ITEM() )
  3123. fp = static_cast<BOARD_ITEM*>( item )->GetParentFootprint();
  3124. // If the selection contains items that are not footprints, then don't restrict
  3125. // whether we deselect the item or not.
  3126. if( !fp )
  3127. {
  3128. single_fp = nullptr;
  3129. break;
  3130. }
  3131. else if( !single_fp )
  3132. {
  3133. single_fp = fp;
  3134. }
  3135. // If the selection contains items from multiple footprints, then don't restrict
  3136. // whether we deselect the item or not.
  3137. else if( single_fp != fp )
  3138. {
  3139. single_fp = nullptr;
  3140. break;
  3141. }
  3142. }
  3143. }
  3144. auto visibleLayers =
  3145. [&]() -> LSET
  3146. {
  3147. if( m_isFootprintEditor )
  3148. {
  3149. LSET set;
  3150. for( PCB_LAYER_ID layer : LSET::AllLayersMask() )
  3151. set.set( layer, view()->IsLayerVisible( layer ) );
  3152. return set;
  3153. }
  3154. else
  3155. {
  3156. return board()->GetVisibleLayers();
  3157. }
  3158. };
  3159. LSET layers = visibleLayers();
  3160. if( settings->GetHighContrast() )
  3161. {
  3162. layers.reset();
  3163. const std::set<int> activeLayers = settings->GetHighContrastLayers();
  3164. for( int layer : activeLayers )
  3165. {
  3166. if( layer >= 0 && layer < PCB_LAYER_ID_COUNT )
  3167. layers.set( layer );
  3168. }
  3169. }
  3170. // Iterate from the back so we don't have to worry about removals.
  3171. for( int i = aCollector.GetCount() - 1; i >= 0; --i )
  3172. {
  3173. BOARD_ITEM* item = aCollector[i];
  3174. FOOTPRINT* fp = dyn_cast<FOOTPRINT*>( item );
  3175. if( !fp )
  3176. continue;
  3177. // Make footprints not difficult to select in high-contrast modes.
  3178. if( layers[fp->GetLayer()] )
  3179. continue;
  3180. BOX2I bbox = fp->GetLayerBoundingBox( layers );
  3181. // If the point clicked is not inside the visible bounding box, we can also remove it.
  3182. if( !bbox.Contains( aWhere) )
  3183. aCollector.Remove( item );
  3184. bool has_hit = false;
  3185. for( PCB_LAYER_ID layer : layers )
  3186. {
  3187. if( fp->HitTestOnLayer( extents, false, layer ) )
  3188. {
  3189. has_hit = true;
  3190. break;
  3191. }
  3192. }
  3193. // If the point is outside of the visible bounding box, we can remove it.
  3194. if( !has_hit )
  3195. {
  3196. aCollector.Remove( item );
  3197. }
  3198. // Do not require a direct hit on this fp if the existing selection only contains
  3199. // this fp's items. This allows you to have a selection of pads from a single
  3200. // footprint and still click in the center of the footprint to select it.
  3201. else if( single_fp )
  3202. {
  3203. if( fp == single_fp )
  3204. continue;
  3205. }
  3206. else if( need_direct_hit )
  3207. {
  3208. has_hit = false;
  3209. for( PCB_LAYER_ID layer : layers )
  3210. {
  3211. if( fp->HitTestOnLayer( aWhere, layer ) )
  3212. {
  3213. has_hit = true;
  3214. break;
  3215. }
  3216. }
  3217. if( !has_hit )
  3218. aCollector.Remove( item );
  3219. }
  3220. }
  3221. }
  3222. int PCB_SELECTION_TOOL::updateSelection( const TOOL_EVENT& aEvent )
  3223. {
  3224. getView()->Update( &m_selection );
  3225. getView()->Update( &m_enteredGroupOverlay );
  3226. return 0;
  3227. }
  3228. int PCB_SELECTION_TOOL::SelectColumns( const TOOL_EVENT& aEvent )
  3229. {
  3230. std::set<std::pair<PCB_TABLE*, int>> columns;
  3231. bool added = false;
  3232. for( EDA_ITEM* item : m_selection )
  3233. {
  3234. if( PCB_TABLECELL* cell = dynamic_cast<PCB_TABLECELL*>( item ) )
  3235. {
  3236. PCB_TABLE* table = static_cast<PCB_TABLE*>( cell->GetParent() );
  3237. columns.insert( std::make_pair( table, cell->GetColumn() ) );
  3238. }
  3239. }
  3240. for( auto& [ table, col ] : columns )
  3241. {
  3242. for( int row = 0; row < table->GetRowCount(); ++row )
  3243. {
  3244. PCB_TABLECELL* cell = table->GetCell( row, col );
  3245. if( !cell->IsSelected() )
  3246. {
  3247. select( table->GetCell( row, col ) );
  3248. added = true;
  3249. }
  3250. }
  3251. }
  3252. if( added )
  3253. m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
  3254. return 0;
  3255. }
  3256. int PCB_SELECTION_TOOL::SelectRows( const TOOL_EVENT& aEvent )
  3257. {
  3258. std::set<std::pair<PCB_TABLE*, int>> rows;
  3259. bool added = false;
  3260. for( EDA_ITEM* item : m_selection )
  3261. {
  3262. if( PCB_TABLECELL* cell = dynamic_cast<PCB_TABLECELL*>( item ) )
  3263. {
  3264. PCB_TABLE* table = static_cast<PCB_TABLE*>( cell->GetParent() );
  3265. rows.insert( std::make_pair( table, cell->GetRow() ) );
  3266. }
  3267. }
  3268. for( auto& [ table, row ] : rows )
  3269. {
  3270. for( int col = 0; col < table->GetColCount(); ++col )
  3271. {
  3272. PCB_TABLECELL* cell = table->GetCell( row, col );
  3273. if( !cell->IsSelected() )
  3274. {
  3275. select( table->GetCell( row, col ) );
  3276. added = true;
  3277. }
  3278. }
  3279. }
  3280. if( added )
  3281. m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
  3282. return 0;
  3283. }
  3284. int PCB_SELECTION_TOOL::SelectTable( const TOOL_EVENT& aEvent )
  3285. {
  3286. std::set<PCB_TABLE*> tables;
  3287. bool added = false;
  3288. for( EDA_ITEM* item : m_selection )
  3289. {
  3290. if( PCB_TABLECELL* cell = dynamic_cast<PCB_TABLECELL*>( item ) )
  3291. tables.insert( static_cast<PCB_TABLE*>( cell->GetParent() ) );
  3292. }
  3293. ClearSelection();
  3294. for( PCB_TABLE* table : tables )
  3295. {
  3296. if( !table->IsSelected() )
  3297. {
  3298. select( table );
  3299. added = true;
  3300. }
  3301. }
  3302. if( added )
  3303. m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
  3304. return 0;
  3305. }
  3306. void PCB_SELECTION_TOOL::setTransitions()
  3307. {
  3308. Go( &PCB_SELECTION_TOOL::UpdateMenu, ACTIONS::updateMenu.MakeEvent() );
  3309. Go( &PCB_SELECTION_TOOL::Main, ACTIONS::selectionActivate.MakeEvent() );
  3310. Go( &PCB_SELECTION_TOOL::CursorSelection, ACTIONS::selectionCursor.MakeEvent() );
  3311. Go( &PCB_SELECTION_TOOL::ClearSelection, ACTIONS::selectionClear.MakeEvent() );
  3312. Go( &PCB_SELECTION_TOOL::AddItemToSel, ACTIONS::selectItem.MakeEvent() );
  3313. Go( &PCB_SELECTION_TOOL::AddItemsToSel, ACTIONS::selectItems.MakeEvent() );
  3314. Go( &PCB_SELECTION_TOOL::RemoveItemFromSel, ACTIONS::unselectItem.MakeEvent() );
  3315. Go( &PCB_SELECTION_TOOL::RemoveItemsFromSel, ACTIONS::unselectItems.MakeEvent() );
  3316. Go( &PCB_SELECTION_TOOL::ReselectItem, ACTIONS::reselectItem.MakeEvent() );
  3317. Go( &PCB_SELECTION_TOOL::SelectionMenu, ACTIONS::selectionMenu.MakeEvent() );
  3318. Go( &PCB_SELECTION_TOOL::filterSelection, PCB_ACTIONS::filterSelection.MakeEvent() );
  3319. Go( &PCB_SELECTION_TOOL::expandConnection, PCB_ACTIONS::selectConnection.MakeEvent() );
  3320. Go( &PCB_SELECTION_TOOL::unrouteSelected, PCB_ACTIONS::unrouteSelected.MakeEvent() );
  3321. Go( &PCB_SELECTION_TOOL::unrouteSegment, PCB_ACTIONS::unrouteSegment.MakeEvent() );
  3322. Go( &PCB_SELECTION_TOOL::selectNet, PCB_ACTIONS::selectNet.MakeEvent() );
  3323. Go( &PCB_SELECTION_TOOL::selectNet, PCB_ACTIONS::deselectNet.MakeEvent() );
  3324. Go( &PCB_SELECTION_TOOL::selectUnconnected, PCB_ACTIONS::selectUnconnected.MakeEvent() );
  3325. Go( &PCB_SELECTION_TOOL::grabUnconnected, PCB_ACTIONS::grabUnconnected.MakeEvent() );
  3326. Go( &PCB_SELECTION_TOOL::syncSelection, PCB_ACTIONS::syncSelection.MakeEvent() );
  3327. Go( &PCB_SELECTION_TOOL::syncSelectionWithNets,
  3328. PCB_ACTIONS::syncSelectionWithNets.MakeEvent() );
  3329. Go( &PCB_SELECTION_TOOL::selectSameSheet, PCB_ACTIONS::selectSameSheet.MakeEvent() );
  3330. Go( &PCB_SELECTION_TOOL::selectSheetContents,
  3331. PCB_ACTIONS::selectOnSheetFromEeschema.MakeEvent() );
  3332. Go( &PCB_SELECTION_TOOL::updateSelection, EVENTS::SelectedItemsModified );
  3333. Go( &PCB_SELECTION_TOOL::updateSelection, EVENTS::SelectedItemsMoved );
  3334. Go( &PCB_SELECTION_TOOL::SelectColumns, ACTIONS::selectColumns.MakeEvent() );
  3335. Go( &PCB_SELECTION_TOOL::SelectRows, ACTIONS::selectRows.MakeEvent() );
  3336. Go( &PCB_SELECTION_TOOL::SelectTable, ACTIONS::selectTable.MakeEvent() );
  3337. Go( &PCB_SELECTION_TOOL::SelectAll, ACTIONS::selectAll.MakeEvent() );
  3338. Go( &PCB_SELECTION_TOOL::UnselectAll, ACTIONS::unselectAll.MakeEvent() );
  3339. Go( &PCB_SELECTION_TOOL::disambiguateCursor, EVENTS::DisambiguatePoint );
  3340. }