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.

1335 lines
39 KiB

  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2019 CERN
  5. * Copyright (C) 2019 KiCad Developers, see AUTHORS.txt for contributors.
  6. *
  7. * This program is free software; you can redistribute it and/or
  8. * modify it under the terms of the GNU General Public License
  9. * as published by the Free Software Foundation; either version 2
  10. * of the License, or (at your option) any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License
  18. * along with this program; if not, you may find one here:
  19. * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  20. * or you may search the http://www.gnu.org website for the version 2 license,
  21. * or you may write to the Free Software Foundation, Inc.,
  22. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  23. */
  24. #include <class_libentry.h>
  25. #include <core/typeinfo.h>
  26. #include <ee_actions.h>
  27. #include <ee_collectors.h>
  28. #include <ee_selection_tool.h>
  29. #include <eeschema_id.h> // For MAX_SELECT_ITEM_IDS
  30. #include <lib_edit_frame.h>
  31. #include <lib_item.h>
  32. #include <lib_view_frame.h>
  33. #include <math/util.h>
  34. #include <menus_helpers.h>
  35. #include <painter.h>
  36. #include <preview_items/selection_area.h>
  37. #include <sch_base_frame.h>
  38. #include <sch_component.h>
  39. #include <sch_edit_frame.h>
  40. #include <sch_field.h>
  41. #include <sch_item.h>
  42. #include <sch_line.h>
  43. #include <sch_sheet.h>
  44. #include <tool/tool_event.h>
  45. #include <tool/tool_manager.h>
  46. #include <tools/sch_line_wire_bus_tool.h>
  47. #include <view/view.h>
  48. #include <view/view_controls.h>
  49. #include <view/view_group.h>
  50. SELECTION_CONDITION EE_CONDITIONS::Empty = [] (const SELECTION& aSelection )
  51. {
  52. return aSelection.Empty();
  53. };
  54. SELECTION_CONDITION EE_CONDITIONS::Idle = [] (const SELECTION& aSelection )
  55. {
  56. return ( !aSelection.Front() || aSelection.Front()->GetEditFlags() == 0 );
  57. };
  58. SELECTION_CONDITION EE_CONDITIONS::IdleSelection = [] (const SELECTION& aSelection )
  59. {
  60. return ( aSelection.Front() && aSelection.Front()->GetEditFlags() == 0 );
  61. };
  62. SELECTION_CONDITION EE_CONDITIONS::SingleSymbol = [] (const SELECTION& aSel )
  63. {
  64. if( aSel.GetSize() == 1 )
  65. {
  66. SCH_COMPONENT* comp = dynamic_cast<SCH_COMPONENT*>( aSel.Front() );
  67. if( comp )
  68. {
  69. return !comp->GetPartRef() || !comp->GetPartRef()->IsPower();
  70. }
  71. }
  72. return false;
  73. };
  74. SELECTION_CONDITION EE_CONDITIONS::SingleDeMorganSymbol = [] ( const SELECTION& aSel )
  75. {
  76. if( aSel.GetSize() == 1 )
  77. {
  78. SCH_COMPONENT* comp = dynamic_cast<SCH_COMPONENT*>( aSel.Front() );
  79. if( comp )
  80. {
  81. return comp->GetPartRef() && comp->GetPartRef()->HasConversion();
  82. }
  83. }
  84. return false;
  85. };
  86. SELECTION_CONDITION EE_CONDITIONS::SingleMultiUnitSymbol = [] ( const SELECTION& aSel )
  87. {
  88. if( aSel.GetSize() == 1 )
  89. {
  90. SCH_COMPONENT* comp = dynamic_cast<SCH_COMPONENT*>( aSel.Front() );
  91. if( comp )
  92. {
  93. return comp->GetPartRef() && comp->GetPartRef()->GetUnitCount() >= 2;
  94. }
  95. }
  96. return false;
  97. };
  98. #define HITTEST_THRESHOLD_PIXELS 5
  99. EE_SELECTION_TOOL::EE_SELECTION_TOOL() :
  100. TOOL_INTERACTIVE( "eeschema.InteractiveSelection" ),
  101. m_frame( nullptr ),
  102. m_additive( false ),
  103. m_subtractive( false ),
  104. m_exclusive_or( false ),
  105. m_multiple( false ),
  106. m_skip_heuristics( false ),
  107. m_isLibEdit( false ),
  108. m_isLibView( false ),
  109. m_unit( 0 ),
  110. m_convert( 0 )
  111. {
  112. }
  113. EE_SELECTION_TOOL::~EE_SELECTION_TOOL()
  114. {
  115. getView()->Remove( &m_selection );
  116. }
  117. using E_C = EE_CONDITIONS;
  118. bool EE_SELECTION_TOOL::Init()
  119. {
  120. m_frame = getEditFrame<SCH_BASE_FRAME>();
  121. LIB_VIEW_FRAME* libViewFrame = dynamic_cast<LIB_VIEW_FRAME*>( m_frame );
  122. LIB_EDIT_FRAME* libEditFrame = dynamic_cast<LIB_EDIT_FRAME*>( m_frame );
  123. if( libEditFrame )
  124. {
  125. m_isLibEdit = true;
  126. m_unit = libEditFrame->GetUnit();
  127. m_convert = libEditFrame->GetConvert();
  128. }
  129. else
  130. m_isLibView = libViewFrame != nullptr;
  131. static KICAD_T wireOrBusTypes[] = { SCH_LINE_LOCATE_WIRE_T, SCH_LINE_LOCATE_BUS_T, EOT };
  132. auto wireSelection = E_C::MoreThan( 0 ) && E_C::OnlyType( SCH_LINE_LOCATE_WIRE_T );
  133. auto busSelection = E_C::MoreThan( 0 ) && E_C::OnlyType( SCH_LINE_LOCATE_BUS_T );
  134. auto wireOrBusSelection = E_C::MoreThan( 0 ) && E_C::OnlyTypes( wireOrBusTypes );
  135. auto sheetSelection = E_C::Count( 1 ) && E_C::OnlyType( SCH_SHEET_T );
  136. auto schEditCondition = [this] ( const SELECTION& aSel ) {
  137. return !m_isLibEdit && !m_isLibView;
  138. };
  139. auto belowRootSheetCondition = [this] ( const SELECTION& aSel ) {
  140. return !m_isLibEdit && !m_isLibView && g_CurrentSheet->Last() != g_RootSheet;
  141. };
  142. auto havePartCondition = [ this ] ( const SELECTION& sel ) {
  143. return m_isLibEdit && ( (LIB_EDIT_FRAME*) m_frame )->GetCurPart();
  144. };
  145. auto& menu = m_menu.GetMenu();
  146. menu.AddItem( EE_ACTIONS::enterSheet, sheetSelection && EE_CONDITIONS::Idle, 1 );
  147. menu.AddItem( EE_ACTIONS::explicitCrossProbe, sheetSelection && EE_CONDITIONS::Idle, 1 );
  148. menu.AddItem( EE_ACTIONS::leaveSheet, belowRootSheetCondition, 1 );
  149. menu.AddSeparator( 100 );
  150. menu.AddItem( EE_ACTIONS::drawWire, schEditCondition && EE_CONDITIONS::Empty, 100 );
  151. menu.AddItem( EE_ACTIONS::drawBus, schEditCondition && EE_CONDITIONS::Empty, 100 );
  152. menu.AddSeparator( 100 );
  153. menu.AddItem( EE_ACTIONS::finishWire, SCH_LINE_WIRE_BUS_TOOL::IsDrawingWire, 100 );
  154. menu.AddSeparator( 100 );
  155. menu.AddItem( EE_ACTIONS::finishBus, SCH_LINE_WIRE_BUS_TOOL::IsDrawingBus, 100 );
  156. menu.AddSeparator( 200 );
  157. menu.AddItem( EE_ACTIONS::selectConnection, wireOrBusSelection && EE_CONDITIONS::Idle, 250 );
  158. menu.AddItem( EE_ACTIONS::placeJunction, wireOrBusSelection && EE_CONDITIONS::Idle, 250 );
  159. menu.AddItem( EE_ACTIONS::placeLabel, wireOrBusSelection && EE_CONDITIONS::Idle, 250 );
  160. menu.AddItem( EE_ACTIONS::placeGlobalLabel, wireOrBusSelection && EE_CONDITIONS::Idle, 250 );
  161. menu.AddItem( EE_ACTIONS::placeHierLabel, wireOrBusSelection && EE_CONDITIONS::Idle, 250 );
  162. menu.AddItem( EE_ACTIONS::breakWire, wireSelection && EE_CONDITIONS::Idle, 250 );
  163. menu.AddItem( EE_ACTIONS::breakBus, busSelection && EE_CONDITIONS::Idle, 250 );
  164. menu.AddItem( EE_ACTIONS::importSheetPin, sheetSelection && EE_CONDITIONS::Idle, 250 );
  165. menu.AddSeparator( 400 );
  166. menu.AddItem( EE_ACTIONS::symbolProperties, havePartCondition && EE_CONDITIONS::Empty, 400 );
  167. menu.AddItem( EE_ACTIONS::pinTable, havePartCondition && EE_CONDITIONS::Empty, 400 );
  168. menu.AddSeparator( 1000 );
  169. m_frame->AddStandardSubMenus( m_menu );
  170. return true;
  171. }
  172. void EE_SELECTION_TOOL::Reset( RESET_REASON aReason )
  173. {
  174. m_frame = getEditFrame<SCH_BASE_FRAME>();
  175. if( aReason == TOOL_BASE::MODEL_RELOAD )
  176. {
  177. // Remove pointers to the selected items from containers without changing their
  178. // properties (as they are already deleted while a new sheet is loaded)
  179. m_selection.Clear();
  180. getView()->GetPainter()->GetSettings()->SetHighlight( false );
  181. LIB_EDIT_FRAME* libEditFrame = dynamic_cast<LIB_EDIT_FRAME*>( m_frame );
  182. LIB_VIEW_FRAME* libViewFrame = dynamic_cast<LIB_VIEW_FRAME*>( m_frame );
  183. if( libEditFrame )
  184. {
  185. m_isLibEdit = true;
  186. m_unit = libEditFrame->GetUnit();
  187. m_convert = libEditFrame->GetConvert();
  188. }
  189. else
  190. m_isLibView = libViewFrame != nullptr;
  191. }
  192. else
  193. // Restore previous properties of selected items and remove them from containers
  194. ClearSelection();
  195. // Reinsert the VIEW_GROUP, in case it was removed from the VIEW
  196. getView()->Remove( &m_selection );
  197. getView()->Add( &m_selection );
  198. }
  199. int EE_SELECTION_TOOL::UpdateMenu( const TOOL_EVENT& aEvent )
  200. {
  201. ACTION_MENU* actionMenu = aEvent.Parameter<ACTION_MENU*>();
  202. CONDITIONAL_MENU* conditionalMenu = dynamic_cast<CONDITIONAL_MENU*>( actionMenu );
  203. if( conditionalMenu )
  204. conditionalMenu->Evaluate( m_selection );
  205. if( actionMenu )
  206. actionMenu->UpdateAll();
  207. return 0;
  208. }
  209. int EE_SELECTION_TOOL::Main( const TOOL_EVENT& aEvent )
  210. {
  211. const KICAD_T movableItems[] =
  212. {
  213. SCH_MARKER_T,
  214. SCH_JUNCTION_T,
  215. SCH_NO_CONNECT_T,
  216. SCH_BUS_BUS_ENTRY_T,
  217. SCH_BUS_WIRE_ENTRY_T,
  218. SCH_LINE_T,
  219. SCH_BITMAP_T,
  220. SCH_TEXT_T,
  221. SCH_LABEL_T,
  222. SCH_GLOBAL_LABEL_T,
  223. SCH_HIER_LABEL_T,
  224. SCH_FIELD_T,
  225. SCH_COMPONENT_T,
  226. SCH_SHEET_PIN_T,
  227. SCH_SHEET_T,
  228. EOT
  229. };
  230. // Main loop: keep receiving events
  231. while( TOOL_EVENT* evt = Wait() )
  232. {
  233. if( m_frame->ToolStackIsEmpty() )
  234. m_frame->GetCanvas()->SetCurrentCursor( wxCURSOR_ARROW );
  235. m_additive = m_subtractive = m_exclusive_or = false;
  236. if( evt->Modifier( MD_SHIFT ) && evt->Modifier( MD_CTRL ) )
  237. m_subtractive = true;
  238. else if( evt->Modifier( MD_SHIFT ) )
  239. m_additive = true;
  240. else if( evt->Modifier( MD_CTRL ) )
  241. m_exclusive_or = true;
  242. // Is the user requesting that the selection list include all possible
  243. // items without removing less likely selection candidates
  244. m_skip_heuristics = !!evt->Modifier( MD_ALT );
  245. // Single click? Select single object
  246. if( evt->IsClick( BUT_LEFT ) )
  247. {
  248. SelectPoint( evt->Position(), EE_COLLECTOR::AllItems, nullptr, false,
  249. m_additive, m_subtractive, m_exclusive_or );
  250. }
  251. // right click? if there is any object - show the context menu
  252. else if( evt->IsClick( BUT_RIGHT ) )
  253. {
  254. bool selectionCancelled = false;
  255. if( m_selection.Empty() ||
  256. !m_selection.GetBoundingBox().Contains( wxPoint( evt->Position() ) ) )
  257. {
  258. ClearSelection();
  259. SelectPoint( evt->Position(), EE_COLLECTOR::AllItems, &selectionCancelled );
  260. m_selection.SetIsHover( true );
  261. }
  262. if( !selectionCancelled )
  263. m_menu.ShowContextMenu( m_selection );
  264. }
  265. // double click? Display the properties window
  266. else if( evt->IsDblClick( BUT_LEFT ) )
  267. {
  268. if( m_selection.Empty() )
  269. SelectPoint( evt->Position());
  270. EDA_ITEM* item = m_selection.Front();
  271. if( item && item->Type() == SCH_SHEET_T )
  272. m_toolMgr->RunAction( EE_ACTIONS::enterSheet );
  273. else
  274. m_toolMgr->RunAction( EE_ACTIONS::properties );
  275. }
  276. // drag with LMB? Select multiple objects (or at least draw a selection box) or drag them
  277. else if( evt->IsDrag( BUT_LEFT ) )
  278. {
  279. if( m_additive || m_subtractive || m_exclusive_or || m_frame->GetDragSelects() )
  280. {
  281. selectMultiple();
  282. }
  283. else
  284. {
  285. // selection is empty? try to start dragging the item under the point where drag
  286. // started
  287. if( m_selection.Empty() )
  288. m_selection = RequestSelection( movableItems );
  289. // Check if dragging has started within any of selected items bounding box
  290. if( selectionContains( evt->Position() ) )
  291. {
  292. // Yes -> run the move tool and wait till it finishes
  293. m_toolMgr->InvokeTool( "eeschema.InteractiveMove" );
  294. }
  295. else
  296. {
  297. // No -> drag a selection box
  298. selectMultiple();
  299. }
  300. }
  301. }
  302. // context sub-menu selection? Handle unit selection or bus unfolding
  303. else if( evt->Category() == TC_COMMAND && evt->Action() == TA_CHOICE_MENU_CHOICE )
  304. {
  305. if( evt->GetCommandId().get() >= ID_POPUP_SCH_SELECT_UNIT_CMP
  306. && evt->GetCommandId().get() <= ID_POPUP_SCH_SELECT_UNIT_CMP_MAX )
  307. {
  308. SCH_COMPONENT* component = dynamic_cast<SCH_COMPONENT*>( m_selection.Front() );
  309. int unit = evt->GetCommandId().get() - ID_POPUP_SCH_SELECT_UNIT_CMP;
  310. if( component )
  311. static_cast<SCH_EDIT_FRAME*>( m_frame )->SelectUnit( component, unit );
  312. }
  313. else if( evt->GetCommandId().get() >= ID_POPUP_SCH_UNFOLD_BUS
  314. && evt->GetCommandId().get() <= ID_POPUP_SCH_UNFOLD_BUS_END )
  315. {
  316. wxString* net = new wxString( *evt->Parameter<wxString*>() );
  317. m_toolMgr->RunAction( EE_ACTIONS::unfoldBus, true, net );
  318. }
  319. }
  320. else if( evt->IsCancelInteractive() )
  321. {
  322. ClearSelection();
  323. }
  324. else if( evt->Action() == TA_UNDO_REDO_PRE )
  325. {
  326. ClearSelection();
  327. }
  328. else
  329. evt->SetPassEvent();
  330. }
  331. // This tool is supposed to be active forever
  332. assert( false );
  333. return 0;
  334. }
  335. EE_SELECTION& EE_SELECTION_TOOL::GetSelection()
  336. {
  337. return m_selection;
  338. }
  339. EDA_ITEM* EE_SELECTION_TOOL::SelectPoint( const VECTOR2I& aWhere, const KICAD_T* aFilterList,
  340. bool* aSelectionCancelledFlag, bool aCheckLocked,
  341. bool aAdd, bool aSubtract, bool aExclusiveOr )
  342. {
  343. EE_COLLECTOR collector;
  344. collector.m_Threshold = KiROUND( getView()->ToWorld( HITTEST_THRESHOLD_PIXELS ) );
  345. if( m_isLibEdit )
  346. {
  347. auto part = static_cast<LIB_EDIT_FRAME*>( m_frame )->GetCurPart();
  348. if( !part )
  349. return nullptr;
  350. collector.Collect( part->GetDrawItems(), aFilterList, (wxPoint) aWhere, m_unit, m_convert );
  351. }
  352. else
  353. collector.Collect( m_frame->GetScreen(), aFilterList, (wxPoint) aWhere, m_unit, m_convert );
  354. // Post-process collected items
  355. for( int i = collector.GetCount() - 1; i >= 0; --i )
  356. {
  357. if( !Selectable( collector[ i ] ) )
  358. {
  359. collector.Remove( i );
  360. continue;
  361. }
  362. if( aCheckLocked && collector[ i ]->IsLocked() )
  363. {
  364. collector.Remove( i );
  365. continue;
  366. }
  367. // SelectPoint, unlike other selection routines, can select line ends
  368. if( collector[ i ]->Type() == SCH_LINE_T )
  369. {
  370. SCH_LINE* line = (SCH_LINE*) collector[ i ];
  371. line->ClearFlags( STARTPOINT | ENDPOINT );
  372. if( HitTestPoints( line->GetStartPoint(), (wxPoint) aWhere, collector.m_Threshold ) )
  373. line->SetFlags( STARTPOINT );
  374. else if (HitTestPoints( line->GetEndPoint(), (wxPoint) aWhere, collector.m_Threshold ) )
  375. line->SetFlags( ENDPOINT );
  376. else
  377. line->SetFlags( STARTPOINT | ENDPOINT );
  378. }
  379. }
  380. m_selection.ClearReferencePoint();
  381. // Apply some ugly heuristics to avoid disambiguation menus whenever possible
  382. if( collector.GetCount() > 1 && !m_skip_heuristics )
  383. {
  384. GuessSelectionCandidates( collector, aWhere );
  385. }
  386. // If still more than one item we're going to have to ask the user.
  387. if( collector.GetCount() > 1 )
  388. {
  389. collector.m_MenuTitle = _( "Clarify Selection" );
  390. // Must call selectionMenu via RunAction() to avoid event-loop contention
  391. m_toolMgr->RunAction( EE_ACTIONS::selectionMenu, true, &collector );
  392. if( collector.m_MenuCancelled )
  393. {
  394. if( aSelectionCancelledFlag )
  395. *aSelectionCancelledFlag = true;
  396. return nullptr;
  397. }
  398. }
  399. if( !aAdd && !aSubtract && !aExclusiveOr )
  400. ClearSelection();
  401. if( collector.GetCount() == 1 )
  402. {
  403. EDA_ITEM* item = collector[ 0 ];
  404. if( aSubtract || ( aExclusiveOr && item->IsSelected() ) )
  405. {
  406. unselect( item );
  407. m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent );
  408. return nullptr;
  409. }
  410. else
  411. {
  412. select( item );
  413. m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
  414. return item;
  415. }
  416. }
  417. return nullptr;
  418. }
  419. void EE_SELECTION_TOOL::GuessSelectionCandidates( EE_COLLECTOR& collector, const VECTOR2I& aPos )
  420. {
  421. // There are certain parent/child and enclosure combinations that can be handled
  422. // automatically.
  423. // Prefer exact hits to sloppy ones
  424. int exactHits = 0;
  425. for( int i = collector.GetCount() - 1; i >= 0; --i )
  426. {
  427. EDA_ITEM* item = collector[ i ];
  428. if( item->HitTest( (wxPoint) aPos, 0 ) )
  429. exactHits++;
  430. }
  431. if( exactHits > 0 && exactHits < collector.GetCount() )
  432. {
  433. for( int i = collector.GetCount() - 1; i >= 0; --i )
  434. {
  435. EDA_ITEM* item = collector[ i ];
  436. if( !item->HitTest( (wxPoint) aPos, 0 ) )
  437. collector.Remove( item );
  438. }
  439. }
  440. // Prefer a non-sheet to a sheet
  441. for( int i = 0; collector.GetCount() == 2 && i < 2; ++i )
  442. {
  443. EDA_ITEM* item = collector[ i ];
  444. EDA_ITEM* other = collector[ ( i + 1 ) % 2 ];
  445. if( item->Type() != SCH_SHEET_T && other->Type() == SCH_SHEET_T )
  446. collector.Remove( other );
  447. }
  448. // Prefer a symbol to a pin or the opposite, when both a symbol and a pin are selected
  449. // We need to be able to select only a pin:
  450. // - to display its characteristics (especially if an ERC is attached to the pin)
  451. // - for cross probing, to select the corresponding pad.
  452. // Note also the case happens only in schematic editor. In symbol editor, the symbol
  453. // itself is never selected
  454. for( int i = 0; collector.GetCount() == 2 && i < 2; ++i )
  455. {
  456. EDA_ITEM* item = collector[ i ];
  457. EDA_ITEM* other = collector[ ( i + 1 ) % 2 ];
  458. if( item->Type() == SCH_COMPONENT_T && other->Type() == SCH_PIN_T )
  459. {
  460. if( m_frame->GetSelectPinSelectSymbol() )
  461. collector.Remove( other );
  462. else
  463. collector.Remove( item );
  464. }
  465. }
  466. // Prefer a field to a symbol
  467. for( int i = 0; collector.GetCount() == 2 && i < 2; ++i )
  468. {
  469. EDA_ITEM* item = collector[ i ];
  470. EDA_ITEM* other = collector[ ( i + 1 ) % 2 ];
  471. if( item->Type() == SCH_FIELD_T && other->Type() == SCH_COMPONENT_T )
  472. collector.Remove( other );
  473. }
  474. // No need for multiple wires at a single point; if there's a junction select that;
  475. // otherwise any of the wires will do
  476. bool junction = false;
  477. bool wiresOnly = true;
  478. for( EDA_ITEM* item : collector )
  479. {
  480. if( item->Type() == SCH_JUNCTION_T )
  481. junction = true;
  482. else if( item->Type() != SCH_LINE_T )
  483. wiresOnly = false;
  484. }
  485. if( wiresOnly )
  486. {
  487. for( int j = collector.GetCount() - 1; j >= 0; --j )
  488. {
  489. if( junction && collector[ j ]->Type() != SCH_JUNCTION_T )
  490. collector.Remove( j );
  491. else if( !junction && j > 0 )
  492. collector.Remove( j );
  493. }
  494. }
  495. }
  496. EE_SELECTION& EE_SELECTION_TOOL::RequestSelection( const KICAD_T aFilterList[] )
  497. {
  498. // Filter an existing selection
  499. if( !m_selection.Empty() )
  500. {
  501. for( int i = m_selection.GetSize() - 1; i >= 0; --i )
  502. {
  503. EDA_ITEM* item = (EDA_ITEM*) m_selection.GetItem( i );
  504. if( !item->IsType( aFilterList ) )
  505. {
  506. unselect( item );
  507. m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent );
  508. }
  509. }
  510. }
  511. // If nothing was selected, or we filtered everything out, do a hover selection
  512. if( m_selection.Empty() )
  513. {
  514. VECTOR2D cursorPos = getViewControls()->GetCursorPosition( true );
  515. ClearSelection();
  516. SelectPoint( cursorPos, aFilterList );
  517. m_selection.SetIsHover( true );
  518. m_selection.ClearReferencePoint();
  519. }
  520. updateReferencePoint();
  521. return m_selection;
  522. }
  523. void EE_SELECTION_TOOL::updateReferencePoint()
  524. {
  525. VECTOR2I refP( 0, 0 );
  526. if( m_selection.Size() > 0 )
  527. {
  528. if( m_isLibEdit )
  529. refP = static_cast<LIB_ITEM*>( m_selection.GetTopLeftItem() )->GetPosition();
  530. else
  531. refP = static_cast<SCH_ITEM*>( m_selection.GetTopLeftItem() )->GetPosition();
  532. }
  533. m_selection.SetReferencePoint( refP );
  534. }
  535. bool EE_SELECTION_TOOL::selectMultiple()
  536. {
  537. bool cancelled = false; // Was the tool cancelled while it was running?
  538. m_multiple = true; // Multiple selection mode is active
  539. KIGFX::VIEW* view = getView();
  540. KIGFX::PREVIEW::SELECTION_AREA area;
  541. view->Add( &area );
  542. while( TOOL_EVENT* evt = Wait() )
  543. {
  544. if( evt->IsCancelInteractive() || evt->IsActivate() )
  545. {
  546. cancelled = true;
  547. break;
  548. }
  549. if( evt->IsDrag( BUT_LEFT ) )
  550. {
  551. if( !m_additive && !m_subtractive && !m_exclusive_or )
  552. ClearSelection();
  553. // Start drawing a selection box
  554. area.SetOrigin( evt->DragOrigin() );
  555. area.SetEnd( evt->Position() );
  556. area.SetAdditive( m_additive );
  557. area.SetSubtractive( m_subtractive );
  558. area.SetExclusiveOr( m_exclusive_or );
  559. view->SetVisible( &area, true );
  560. view->Update( &area );
  561. getViewControls()->SetAutoPan( true );
  562. }
  563. if( evt->IsMouseUp( BUT_LEFT ) )
  564. {
  565. getViewControls()->SetAutoPan( false );
  566. // End drawing the selection box
  567. view->SetVisible( &area, false );
  568. // Mark items within the selection box as selected
  569. std::vector<KIGFX::VIEW::LAYER_ITEM_PAIR> selectedItems;
  570. std::vector<KIGFX::VIEW::LAYER_ITEM_PAIR> sheetPins;
  571. // Filter the view items based on the selection box
  572. BOX2I selectionBox = area.ViewBBox();
  573. view->Query( selectionBox, selectedItems ); // Get the list of selected items
  574. // Sheet pins aren't in the view; add them by hand
  575. for( KIGFX::VIEW::LAYER_ITEM_PAIR& pair : selectedItems )
  576. {
  577. SCH_SHEET* sheet = dynamic_cast<SCH_SHEET*>( pair.first );
  578. if( sheet )
  579. {
  580. int layer = pair.second;
  581. for( SCH_SHEET_PIN* pin : sheet->GetPins() )
  582. sheetPins.emplace_back( KIGFX::VIEW::LAYER_ITEM_PAIR( pin, layer ) );
  583. }
  584. }
  585. selectedItems.insert( selectedItems.end(), sheetPins.begin(), sheetPins.end() );
  586. int width = area.GetEnd().x - area.GetOrigin().x;
  587. int height = area.GetEnd().y - area.GetOrigin().y;
  588. /* Selection mode depends on direction of drag-selection:
  589. * Left > Right : Select objects that are fully enclosed by selection
  590. * Right > Left : Select objects that are crossed by selection
  591. */
  592. bool windowSelection = width >= 0;
  593. bool anyAdded = false;
  594. bool anySubtracted = false;
  595. if( view->IsMirroredX() )
  596. windowSelection = !windowSelection;
  597. // Construct an EDA_RECT to determine EDA_ITEM selection
  598. EDA_RECT selectionRect( (wxPoint) area.GetOrigin(), wxSize( width, height ) );
  599. selectionRect.Normalize();
  600. for( KIGFX::VIEW::LAYER_ITEM_PAIR& pair : selectedItems )
  601. {
  602. EDA_ITEM* item = dynamic_cast<EDA_ITEM*>( pair.first );
  603. if( item && Selectable( item ) && item->HitTest( selectionRect, windowSelection ) )
  604. {
  605. if( m_subtractive || ( m_exclusive_or && item->IsSelected() ) )
  606. {
  607. unselect( item );
  608. anySubtracted = true;
  609. }
  610. else
  611. {
  612. select( item );
  613. item->SetFlags( STARTPOINT | ENDPOINT );
  614. anyAdded = true;
  615. }
  616. }
  617. }
  618. m_selection.SetIsHover( false );
  619. // Inform other potentially interested tools
  620. if( anyAdded )
  621. m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
  622. if( anySubtracted )
  623. m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent );
  624. break; // Stop waiting for events
  625. }
  626. }
  627. getViewControls()->SetAutoPan( false );
  628. // Stop drawing the selection box
  629. view->Remove( &area );
  630. m_multiple = false; // Multiple selection mode is inactive
  631. if( !cancelled )
  632. m_selection.ClearReferencePoint();
  633. return cancelled;
  634. }
  635. static KICAD_T nodeTypes[] =
  636. {
  637. SCH_LINE_LOCATE_WIRE_T,
  638. SCH_LINE_LOCATE_BUS_T,
  639. SCH_BUS_WIRE_ENTRY_T,
  640. SCH_BUS_BUS_ENTRY_T,
  641. SCH_LABEL_T,
  642. SCH_HIER_LABEL_T,
  643. SCH_GLOBAL_LABEL_T,
  644. SCH_SHEET_PIN_T,
  645. SCH_JUNCTION_T,
  646. EOT
  647. };
  648. EDA_ITEM* EE_SELECTION_TOOL::GetNode( VECTOR2I aPosition )
  649. {
  650. EE_COLLECTOR collector;
  651. //TODO(snh): Reimplement after exposing KNN interface
  652. int thresholdMax = KiROUND( getView()->ToWorld( HITTEST_THRESHOLD_PIXELS ) );
  653. for( int threshold : { 0, thresholdMax/2, thresholdMax } )
  654. {
  655. collector.m_Threshold = threshold;
  656. collector.Collect( m_frame->GetScreen(), nodeTypes, (wxPoint) aPosition );
  657. if( collector.GetCount() > 0 )
  658. break;
  659. }
  660. return collector.GetCount() ? collector[ 0 ] : nullptr;
  661. }
  662. int EE_SELECTION_TOOL::SelectNode( const TOOL_EVENT& aEvent )
  663. {
  664. VECTOR2I cursorPos = getViewControls()->GetCursorPosition( !aEvent.Modifier( MD_ALT ) );
  665. SelectPoint( cursorPos, nodeTypes );
  666. return 0;
  667. }
  668. int EE_SELECTION_TOOL::SelectConnection( const TOOL_EVENT& aEvent )
  669. {
  670. static KICAD_T wiresAndBusses[] = { SCH_LINE_LOCATE_WIRE_T, SCH_LINE_LOCATE_BUS_T, EOT };
  671. RequestSelection( wiresAndBusses );
  672. if( m_selection.Empty() )
  673. return 0;
  674. SCH_LINE* line = (SCH_LINE*) m_selection.Front();
  675. EDA_ITEMS items;
  676. m_frame->GetScreen()->ClearDrawingState();
  677. auto conns = m_frame->GetScreen()->MarkConnections( line );
  678. for( auto item : conns )
  679. select( item );
  680. if( m_selection.GetSize() > 1 )
  681. m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
  682. return 0;
  683. }
  684. int EE_SELECTION_TOOL::AddItemToSel( const TOOL_EVENT& aEvent )
  685. {
  686. AddItemToSel( aEvent.Parameter<EDA_ITEM*>() );
  687. m_selection.SetIsHover( false );
  688. return 0;
  689. }
  690. void EE_SELECTION_TOOL::AddItemToSel( EDA_ITEM* aItem, bool aQuietMode )
  691. {
  692. if( aItem )
  693. {
  694. select( aItem );
  695. // Inform other potentially interested tools
  696. if( !aQuietMode )
  697. m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
  698. }
  699. }
  700. int EE_SELECTION_TOOL::AddItemsToSel( const TOOL_EVENT& aEvent )
  701. {
  702. AddItemsToSel( aEvent.Parameter<EDA_ITEMS*>(), false );
  703. m_selection.SetIsHover( false );
  704. return 0;
  705. }
  706. void EE_SELECTION_TOOL::AddItemsToSel( EDA_ITEMS* aList, bool aQuietMode )
  707. {
  708. if( aList )
  709. {
  710. for( EDA_ITEM* item : *aList )
  711. select( item );
  712. // Inform other potentially interested tools
  713. if( !aQuietMode )
  714. m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
  715. }
  716. }
  717. int EE_SELECTION_TOOL::RemoveItemFromSel( const TOOL_EVENT& aEvent )
  718. {
  719. RemoveItemFromSel( aEvent.Parameter<EDA_ITEM*>() );
  720. m_selection.SetIsHover( false );
  721. return 0;
  722. }
  723. void EE_SELECTION_TOOL::RemoveItemFromSel( EDA_ITEM* aItem, bool aQuietMode )
  724. {
  725. if( aItem )
  726. {
  727. unselect( aItem );
  728. // Inform other potentially interested tools
  729. if( !aQuietMode )
  730. m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent );
  731. }
  732. }
  733. int EE_SELECTION_TOOL::RemoveItemsFromSel( const TOOL_EVENT& aEvent )
  734. {
  735. RemoveItemsFromSel( aEvent.Parameter<EDA_ITEMS*>(), false );
  736. m_selection.SetIsHover( false );
  737. return 0;
  738. }
  739. void EE_SELECTION_TOOL::RemoveItemsFromSel( EDA_ITEMS* aList, bool aQuietMode )
  740. {
  741. if( aList )
  742. {
  743. for( EDA_ITEM* item : *aList )
  744. unselect( item );
  745. // Inform other potentially interested tools
  746. if( !aQuietMode )
  747. m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent );
  748. }
  749. }
  750. void EE_SELECTION_TOOL::BrightenItem( EDA_ITEM* aItem )
  751. {
  752. highlight( aItem, BRIGHTENED );
  753. }
  754. void EE_SELECTION_TOOL::UnbrightenItem( EDA_ITEM* aItem )
  755. {
  756. unhighlight( aItem, BRIGHTENED );
  757. }
  758. int EE_SELECTION_TOOL::ClearSelection( const TOOL_EVENT& aEvent )
  759. {
  760. ClearSelection();
  761. return 0;
  762. }
  763. void EE_SELECTION_TOOL::RebuildSelection()
  764. {
  765. m_selection.Clear();
  766. if( m_isLibEdit )
  767. {
  768. LIB_PART* start = static_cast<LIB_EDIT_FRAME*>( m_frame )->GetCurPart();
  769. for( auto& item : start->GetDrawItems() )
  770. {
  771. if( item.IsSelected() )
  772. select( static_cast<EDA_ITEM*>( &item ) );
  773. }
  774. }
  775. else
  776. {
  777. for( auto item : m_frame->GetScreen()->Items() )
  778. {
  779. // If the field and component are selected, only use the component
  780. if( item->IsSelected()
  781. && !( item->Type() == SCH_FIELD_T && item->GetParent()
  782. && item->GetParent()->IsSelected() ) )
  783. {
  784. select( item );
  785. }
  786. }
  787. }
  788. updateReferencePoint();
  789. // Inform other potentially interested tools
  790. m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
  791. }
  792. int EE_SELECTION_TOOL::SelectionMenu( const TOOL_EVENT& aEvent )
  793. {
  794. EE_COLLECTOR* collector = aEvent.Parameter<EE_COLLECTOR*>();
  795. if( !doSelectionMenu( collector ) )
  796. collector->m_MenuCancelled = true;
  797. return 0;
  798. }
  799. bool EE_SELECTION_TOOL::doSelectionMenu( EE_COLLECTOR* aCollector )
  800. {
  801. EDA_ITEM* current = nullptr;
  802. ACTION_MENU menu( true );
  803. int limit = std::min( MAX_SELECT_ITEM_IDS, aCollector->GetCount() );
  804. for( int i = 0; i < limit; ++i )
  805. {
  806. wxString text;
  807. EDA_ITEM* item = ( *aCollector )[i];
  808. text = item->GetSelectMenuText( m_frame->GetUserUnits() );
  809. wxString menuText = wxString::Format("&%d. %s", i + 1, text );
  810. menu.Add( menuText, i + 1, item->GetMenuImage() );
  811. }
  812. if( aCollector->m_MenuTitle.Length() )
  813. menu.SetTitle( aCollector->m_MenuTitle );
  814. menu.SetIcon( info_xpm );
  815. menu.DisplayTitle( true );
  816. SetContextMenu( &menu, CMENU_NOW );
  817. while( TOOL_EVENT* evt = Wait() )
  818. {
  819. if( evt->Action() == TA_CHOICE_MENU_UPDATE )
  820. {
  821. if( current )
  822. unhighlight( current, BRIGHTENED );
  823. int id = *evt->GetCommandId();
  824. // User has pointed an item, so show it in a different way
  825. if( id > 0 && id <= limit )
  826. {
  827. current = ( *aCollector )[id - 1];
  828. highlight( current, BRIGHTENED );
  829. }
  830. else
  831. {
  832. current = nullptr;
  833. }
  834. }
  835. else if( evt->Action() == TA_CHOICE_MENU_CHOICE )
  836. {
  837. if( current )
  838. unhighlight( current, BRIGHTENED );
  839. OPT<int> id = evt->GetCommandId();
  840. // User has selected an item, so this one will be returned
  841. if( id && ( *id > 0 ) )
  842. current = ( *aCollector )[*id - 1];
  843. else
  844. current = nullptr;
  845. break;
  846. }
  847. getView()->UpdateItems();
  848. m_frame->GetCanvas()->Refresh();
  849. }
  850. if( current )
  851. {
  852. unhighlight( current, BRIGHTENED );
  853. getView()->UpdateItems();
  854. m_frame->GetCanvas()->Refresh();
  855. aCollector->Empty();
  856. aCollector->Append( current );
  857. return true;
  858. }
  859. return false;
  860. }
  861. bool EE_SELECTION_TOOL::Selectable( const EDA_ITEM* aItem, bool checkVisibilityOnly ) const
  862. {
  863. // NOTE: in the future this is where eeschema layer/itemtype visibility will be handled
  864. LIB_EDIT_FRAME* symbeditFrame = dynamic_cast< LIB_EDIT_FRAME* >( m_frame );
  865. switch( aItem->Type() )
  866. {
  867. case SCH_PIN_T:
  868. if( !static_cast<const SCH_PIN*>( aItem )->IsVisible() && !m_frame->GetShowAllPins() )
  869. return false;
  870. break;
  871. case LIB_PART_T: // In libedit we do not want to select the symbol itself.
  872. return false;
  873. case LIB_FIELD_T:
  874. {
  875. if( symbeditFrame )
  876. {
  877. LIB_PART* currentPart = symbeditFrame->GetCurPart();
  878. // Nothing in derived symbols is editable at the moment.
  879. if( currentPart && currentPart->IsAlias() )
  880. return false;
  881. }
  882. break;
  883. }
  884. case LIB_ARC_T:
  885. case LIB_CIRCLE_T:
  886. case LIB_TEXT_T:
  887. case LIB_RECTANGLE_T:
  888. case LIB_POLYLINE_T:
  889. case LIB_BEZIER_T:
  890. case LIB_PIN_T:
  891. {
  892. if( symbeditFrame )
  893. {
  894. LIB_ITEM* lib_item = (LIB_ITEM*) aItem;
  895. if( lib_item->GetUnit() && lib_item->GetUnit() != symbeditFrame->GetUnit() )
  896. return false;
  897. if( lib_item->GetConvert() && lib_item->GetConvert() != symbeditFrame->GetConvert() )
  898. return false;
  899. }
  900. break;
  901. }
  902. case SCH_MARKER_T: // Always selectable
  903. return true;
  904. default: // Suppress warnings
  905. break;
  906. }
  907. return true;
  908. }
  909. void EE_SELECTION_TOOL::ClearSelection()
  910. {
  911. if( m_selection.Empty() )
  912. return;
  913. while( m_selection.GetSize() )
  914. unhighlight( (EDA_ITEM*) m_selection.Front(), SELECTED, &m_selection );
  915. getView()->Update( &m_selection );
  916. m_selection.SetIsHover( false );
  917. m_selection.ClearReferencePoint();
  918. // Inform other potentially interested tools
  919. m_toolMgr->ProcessEvent( EVENTS::ClearedEvent );
  920. }
  921. void EE_SELECTION_TOOL::select( EDA_ITEM* aItem )
  922. {
  923. highlight( aItem, SELECTED, &m_selection );
  924. }
  925. void EE_SELECTION_TOOL::unselect( EDA_ITEM* aItem )
  926. {
  927. unhighlight( aItem, SELECTED, &m_selection );
  928. }
  929. void EE_SELECTION_TOOL::highlight( EDA_ITEM* aItem, int aMode, EE_SELECTION* aGroup )
  930. {
  931. KICAD_T itemType = aItem->Type();
  932. if( aMode == SELECTED )
  933. aItem->SetSelected();
  934. else if( aMode == BRIGHTENED )
  935. aItem->SetBrightened();
  936. if( aGroup )
  937. aGroup->Add( aItem );
  938. // Highlight pins and fields. (All the other component children are currently only
  939. // represented in the LIB_PART and will inherit the settings of the parent component.)
  940. if( itemType == SCH_COMPONENT_T )
  941. {
  942. SCH_PIN_PTRS pins = static_cast<SCH_COMPONENT*>( aItem )->GetSchPins( g_CurrentSheet );
  943. for( SCH_PIN* pin : pins )
  944. {
  945. if( aMode == SELECTED )
  946. pin->SetSelected();
  947. else if( aMode == BRIGHTENED )
  948. pin->SetBrightened();
  949. }
  950. std::vector<SCH_FIELD*> fields;
  951. static_cast<SCH_COMPONENT*>( aItem )->GetFields( fields, false );
  952. for( SCH_FIELD* field : fields )
  953. {
  954. if( aMode == SELECTED )
  955. field->SetSelected();
  956. else if( aMode == BRIGHTENED )
  957. field->SetBrightened();
  958. }
  959. }
  960. else if( itemType == SCH_SHEET_T )
  961. {
  962. std::vector<SCH_SHEET_PIN*>& pins = static_cast<SCH_SHEET*>( aItem )->GetPins();
  963. for( SCH_SHEET_PIN* pin : pins )
  964. {
  965. if( aMode == SELECTED )
  966. pin->SetSelected();
  967. else if( aMode == BRIGHTENED )
  968. pin->SetBrightened();
  969. }
  970. }
  971. if( itemType == SCH_PIN_T || itemType == SCH_FIELD_T || itemType == SCH_SHEET_PIN_T )
  972. getView()->Update( aItem->GetParent() );
  973. else
  974. getView()->Update( aItem );
  975. }
  976. void EE_SELECTION_TOOL::unhighlight( EDA_ITEM* aItem, int aMode, EE_SELECTION* aGroup )
  977. {
  978. KICAD_T itemType = aItem->Type();
  979. if( aMode == SELECTED )
  980. aItem->ClearSelected();
  981. else if( aMode == BRIGHTENED )
  982. aItem->ClearBrightened();
  983. if( aGroup )
  984. aGroup->Remove( aItem );
  985. // Unhighlight pins and fields. (All the other component children are currently only
  986. // represented in the LIB_PART.)
  987. if( itemType == SCH_COMPONENT_T )
  988. {
  989. SCH_PIN_PTRS pins = static_cast<SCH_COMPONENT*>( aItem )->GetSchPins( g_CurrentSheet );
  990. for( SCH_PIN* pin : pins )
  991. {
  992. if( aMode == SELECTED )
  993. pin->ClearSelected();
  994. else if( aMode == BRIGHTENED )
  995. pin->ClearBrightened();
  996. }
  997. std::vector<SCH_FIELD*> fields;
  998. static_cast<SCH_COMPONENT*>( aItem )->GetFields( fields, false );
  999. for( SCH_FIELD* field : fields )
  1000. {
  1001. if( aMode == SELECTED )
  1002. field->ClearSelected();
  1003. else if( aMode == BRIGHTENED )
  1004. field->ClearBrightened();
  1005. }
  1006. }
  1007. else if( itemType == SCH_SHEET_T )
  1008. {
  1009. std::vector<SCH_SHEET_PIN*>& pins = static_cast<SCH_SHEET*>( aItem )->GetPins();
  1010. for( SCH_SHEET_PIN* pin : pins )
  1011. {
  1012. if( aMode == SELECTED )
  1013. pin->ClearSelected();
  1014. else if( aMode == BRIGHTENED )
  1015. pin->ClearBrightened();
  1016. }
  1017. }
  1018. if( itemType == SCH_PIN_T || itemType == SCH_FIELD_T || itemType == SCH_SHEET_PIN_T )
  1019. getView()->Update( aItem->GetParent() );
  1020. else
  1021. getView()->Update( aItem );
  1022. }
  1023. bool EE_SELECTION_TOOL::selectionContains( const VECTOR2I& aPoint ) const
  1024. {
  1025. const unsigned GRIP_MARGIN = 20;
  1026. VECTOR2I margin = getView()->ToWorld( VECTOR2I( GRIP_MARGIN, GRIP_MARGIN ), false );
  1027. // Check if the point is located within any of the currently selected items bounding boxes
  1028. for( auto item : m_selection )
  1029. {
  1030. BOX2I itemBox = item->ViewBBox();
  1031. itemBox.Inflate( margin.x, margin.y ); // Give some margin for gripping an item
  1032. if( itemBox.Contains( aPoint ) )
  1033. return true;
  1034. }
  1035. return false;
  1036. }
  1037. void EE_SELECTION_TOOL::setTransitions()
  1038. {
  1039. Go( &EE_SELECTION_TOOL::UpdateMenu, ACTIONS::updateMenu.MakeEvent() );
  1040. Go( &EE_SELECTION_TOOL::Main, EE_ACTIONS::selectionActivate.MakeEvent() );
  1041. Go( &EE_SELECTION_TOOL::SelectNode, EE_ACTIONS::selectNode.MakeEvent() );
  1042. Go( &EE_SELECTION_TOOL::SelectConnection, EE_ACTIONS::selectConnection.MakeEvent() );
  1043. Go( &EE_SELECTION_TOOL::ClearSelection, EE_ACTIONS::clearSelection.MakeEvent() );
  1044. Go( &EE_SELECTION_TOOL::AddItemToSel, EE_ACTIONS::addItemToSel.MakeEvent() );
  1045. Go( &EE_SELECTION_TOOL::AddItemsToSel, EE_ACTIONS::addItemsToSel.MakeEvent() );
  1046. Go( &EE_SELECTION_TOOL::RemoveItemFromSel, EE_ACTIONS::removeItemFromSel.MakeEvent() );
  1047. Go( &EE_SELECTION_TOOL::RemoveItemsFromSel, EE_ACTIONS::removeItemsFromSel.MakeEvent() );
  1048. Go( &EE_SELECTION_TOOL::SelectionMenu, EE_ACTIONS::selectionMenu.MakeEvent() );
  1049. }