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.

2676 lines
79 KiB

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