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.

1879 lines
70 KiB

7 years ago
5 years ago
5 years ago
5 years ago
5 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
5 years ago
3 years ago
3 years ago
3 years ago
5 years ago
5 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
5 years ago
5 years ago
3 years ago
3 years ago
3 years ago
  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2019 CERN
  5. * Copyright The 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 <cmath>
  25. #include <wx/log.h>
  26. #include <trigo.h>
  27. #include <gal/graphics_abstraction_layer.h>
  28. #include <tool/tool_manager.h>
  29. #include <tools/ee_grid_helper.h>
  30. #include <tools/sch_selection_tool.h>
  31. #include <tools/sch_line_wire_bus_tool.h>
  32. #include <sch_actions.h>
  33. #include <sch_commit.h>
  34. #include <eda_item.h>
  35. #include <sch_item.h>
  36. #include <sch_symbol.h>
  37. #include <sch_sheet.h>
  38. #include <sch_sheet_pin.h>
  39. #include <sch_line.h>
  40. #include <sch_junction.h>
  41. #include <sch_edit_frame.h>
  42. #include <eeschema_id.h>
  43. #include <pgm_base.h>
  44. #include <view/view_controls.h>
  45. #include <settings/settings_manager.h>
  46. #include "sch_move_tool.h"
  47. // For adding to or removing from selections
  48. #define QUIET_MODE true
  49. SCH_MOVE_TOOL::SCH_MOVE_TOOL() :
  50. SCH_TOOL_BASE<SCH_EDIT_FRAME>( "eeschema.InteractiveMove" ),
  51. m_inMoveTool( false ),
  52. m_moveInProgress( false ),
  53. m_isDrag( false ),
  54. m_moveOffset( 0, 0 )
  55. {
  56. }
  57. bool SCH_MOVE_TOOL::Init()
  58. {
  59. SCH_TOOL_BASE::Init();
  60. auto moveCondition =
  61. []( const SELECTION& aSel )
  62. {
  63. if( aSel.Empty() || SELECTION_CONDITIONS::OnlyTypes( { SCH_MARKER_T } )( aSel ) )
  64. return false;
  65. if( SCH_LINE_WIRE_BUS_TOOL::IsDrawingLineWireOrBus( aSel ) )
  66. return false;
  67. return true;
  68. };
  69. // Add move actions to the selection tool menu
  70. //
  71. CONDITIONAL_MENU& selToolMenu = m_selectionTool->GetToolMenu().GetMenu();
  72. selToolMenu.AddItem( SCH_ACTIONS::move, moveCondition, 150 );
  73. selToolMenu.AddItem( SCH_ACTIONS::drag, moveCondition, 150 );
  74. selToolMenu.AddItem( SCH_ACTIONS::alignToGrid, moveCondition, 150 );
  75. return true;
  76. }
  77. void SCH_MOVE_TOOL::orthoLineDrag( SCH_COMMIT* aCommit, SCH_LINE* line, const VECTOR2I& splitDelta,
  78. int& xBendCount, int& yBendCount, const EE_GRID_HELPER& grid )
  79. {
  80. // If the move is not the same angle as this move, then we need to do something special with
  81. // the unselected end to maintain orthogonality. Either drag some connected line that is the
  82. // same angle as the move or add two lines to make a 90 degree connection
  83. if( !EDA_ANGLE( splitDelta ).IsParallelTo( line->Angle() ) || line->GetLength() == 0 )
  84. {
  85. VECTOR2I unselectedEnd = line->HasFlag( STARTPOINT ) ? line->GetEndPoint()
  86. : line->GetStartPoint();
  87. VECTOR2I selectedEnd = line->HasFlag( STARTPOINT ) ? line->GetStartPoint()
  88. : line->GetEndPoint();
  89. // Look for pre-existing lines we can drag with us instead of creating new ones
  90. bool foundAttachment = false;
  91. bool foundJunction = false;
  92. bool foundPin = false;
  93. SCH_LINE* foundLine = nullptr;
  94. for( EDA_ITEM* cItem : m_lineConnectionCache[line] )
  95. {
  96. foundAttachment = true;
  97. // If the move is the same angle as a connected line, we can shrink/extend that line
  98. // endpoint
  99. switch( cItem->Type() )
  100. {
  101. case SCH_LINE_T:
  102. {
  103. SCH_LINE* cLine = static_cast<SCH_LINE*>( cItem );
  104. // A matching angle on a non-zero-length line means lengthen/shorten will work
  105. if( EDA_ANGLE( splitDelta ).IsParallelTo( cLine->Angle() )
  106. && cLine->GetLength() != 0 )
  107. {
  108. foundLine = cLine;
  109. }
  110. // Zero length lines are lines that this algorithm has shortened to 0 so they also
  111. // work but we should prefer using a segment with length and angle matching when
  112. // we can (otherwise the zero length line will draw overlapping segments on them)
  113. if( !foundLine && cLine->GetLength() == 0 )
  114. foundLine = cLine;
  115. break;
  116. }
  117. case SCH_JUNCTION_T:
  118. foundJunction = true;
  119. break;
  120. case SCH_PIN_T:
  121. foundPin = true;
  122. break;
  123. case SCH_SHEET_T:
  124. for( const auto& pair : m_specialCaseSheetPins )
  125. {
  126. if( pair.first->IsConnected( selectedEnd ) )
  127. {
  128. foundPin = true;
  129. break;
  130. }
  131. }
  132. break;
  133. default:
  134. break;
  135. }
  136. }
  137. // Ok... what if our original line is length zero from moving in its direction, and the
  138. // last added segment of the 90 bend we are connected to is zero from moving it in its
  139. // direction after it was added?
  140. //
  141. // If we are moving in original direction, we should lengthen the original drag wire.
  142. // Otherwise we should lengthen the new wire.
  143. bool preferOriginalLine = false;
  144. if( foundLine
  145. && foundLine->GetLength() == 0
  146. && line->GetLength() == 0
  147. && EDA_ANGLE( splitDelta ).IsParallelTo( line->GetStoredAngle() ) )
  148. {
  149. preferOriginalLine = true;
  150. }
  151. // If we have found an attachment, but not a line, we want to check if it's a junction.
  152. // These are special-cased and get a single line added instead of a 90-degree bend. Except
  153. // when we're on a pin, because pins always need bends, and junctions are just added to
  154. // pins for visual clarity.
  155. else if( !foundLine && foundJunction && !foundPin )
  156. {
  157. // Create a new wire ending at the unselected end
  158. foundLine = new SCH_LINE( unselectedEnd, line->GetLayer() );
  159. foundLine->SetFlags( IS_NEW );
  160. foundLine->SetLastResolvedState( line );
  161. m_frame->AddToScreen( foundLine, m_frame->GetScreen() );
  162. m_newDragLines.insert( foundLine );
  163. // We just broke off of the existing items, so replace all of them with our new
  164. // end connection.
  165. m_lineConnectionCache[foundLine] = m_lineConnectionCache[line];
  166. m_lineConnectionCache[line].clear();
  167. m_lineConnectionCache[line].emplace_back( foundLine );
  168. }
  169. // We want to drag our found line if it's in the same angle as the move or zero length,
  170. // but if the original drag line is also zero and the same original angle we should extend
  171. // that one first
  172. if( foundLine && !preferOriginalLine )
  173. {
  174. // Move the connected line found oriented in the direction of our move.
  175. //
  176. // Make sure we grab the right endpoint, it's not always STARTPOINT since the user can
  177. // draw a box of lines. We need to only move one though, and preferably the start point,
  178. // in case we have a zero length line that we are extending (we want the foundLine
  179. // start point to be attached to the unselected end of our drag line).
  180. //
  181. // Also, new lines are added already so they'll be in the undo list, skip adding them.
  182. if( !foundLine->HasFlag( IS_CHANGED ) && !foundLine->HasFlag( IS_NEW ) )
  183. {
  184. aCommit->Modify( (SCH_ITEM*) foundLine, m_frame->GetScreen() );
  185. if( !foundLine->IsSelected() )
  186. m_changedDragLines.insert( foundLine );
  187. }
  188. if( foundLine->GetStartPoint() == unselectedEnd )
  189. foundLine->MoveStart( splitDelta );
  190. else if( foundLine->GetEndPoint() == unselectedEnd )
  191. foundLine->MoveEnd( splitDelta );
  192. updateItem( foundLine, true );
  193. SCH_LINE* bendLine = nullptr;
  194. if( m_lineConnectionCache.count( foundLine ) == 1
  195. && m_lineConnectionCache[foundLine][0]->Type() == SCH_LINE_T )
  196. {
  197. bendLine = static_cast<SCH_LINE*>( m_lineConnectionCache[foundLine][0] );
  198. }
  199. // Remerge segments we've created if this is a segment that we've added whose only
  200. // other connection is also an added segment
  201. //
  202. // bendLine is first added segment at the original attachment point, foundLine is the
  203. // orthogonal line between bendLine and this line
  204. if( foundLine->HasFlag( IS_NEW )
  205. && foundLine->GetLength() == 0
  206. && bendLine && bendLine->HasFlag( IS_NEW ) )
  207. {
  208. if( line->HasFlag( STARTPOINT ) )
  209. line->SetEndPoint( bendLine->GetEndPoint() );
  210. else
  211. line->SetStartPoint( bendLine->GetEndPoint() );
  212. // Update our cache of the connected items.
  213. // First, re-attach our drag labels to the original line being re-merged.
  214. for( EDA_ITEM* candidate : m_lineConnectionCache[bendLine] )
  215. {
  216. SCH_LABEL_BASE* label = dynamic_cast<SCH_LABEL_BASE*>( candidate );
  217. if( label && m_specialCaseLabels.count( label ) )
  218. m_specialCaseLabels[label].attachedLine = line;
  219. }
  220. m_lineConnectionCache[line] = m_lineConnectionCache[bendLine];
  221. m_lineConnectionCache[bendLine].clear();
  222. m_lineConnectionCache[foundLine].clear();
  223. m_frame->RemoveFromScreen( bendLine, m_frame->GetScreen() );
  224. m_frame->RemoveFromScreen( foundLine, m_frame->GetScreen() );
  225. m_newDragLines.erase( bendLine );
  226. m_newDragLines.erase( foundLine );
  227. delete bendLine;
  228. delete foundLine;
  229. }
  230. //Ok, move the unselected end of our item
  231. else
  232. {
  233. if( line->HasFlag( STARTPOINT ) )
  234. line->MoveEnd( splitDelta );
  235. else
  236. line->MoveStart( splitDelta );
  237. }
  238. updateItem( line, true );
  239. }
  240. else if( line->GetLength() == 0 )
  241. {
  242. // We didn't find another line to shorten/lengthen, (or we did but it's also zero)
  243. // so now is a good time to use our existing zero-length original line
  244. }
  245. // Either no line was at the "right" angle, or this was a junction, pin, sheet, etc. We
  246. // need to add segments to keep the soon-to-move unselected end connected to these items.
  247. //
  248. // To keep our drag selections all the same, we'll move our unselected end point and then
  249. // put wires between it and its original endpoint.
  250. else if( foundAttachment && line->IsOrthogonal() )
  251. {
  252. VECTOR2D lineGrid = grid.GetGridSize( grid.GetItemGrid( line ) );
  253. // The bend counter handles a group of wires all needing their offset one grid movement
  254. // further out from each other to not overlap. The absolute value stuff finds the
  255. // direction of the line and hence the the bend increment on that axis
  256. unsigned int xMoveBit = splitDelta.x != 0;
  257. unsigned int yMoveBit = splitDelta.y != 0;
  258. int xLength = abs( unselectedEnd.x - selectedEnd.x );
  259. int yLength = abs( unselectedEnd.y - selectedEnd.y );
  260. int xMove = ( xLength - ( xBendCount * lineGrid.x ) )
  261. * sign( selectedEnd.x - unselectedEnd.x );
  262. int yMove = ( yLength - ( yBendCount * lineGrid.y ) )
  263. * sign( selectedEnd.y - unselectedEnd.y );
  264. // Create a new wire ending at the unselected end, we'll move the new wire's start
  265. // point to the unselected end
  266. SCH_LINE* a = new SCH_LINE( unselectedEnd, line->GetLayer() );
  267. a->MoveStart( VECTOR2I( xMove, yMove ) );
  268. a->SetFlags( IS_NEW );
  269. a->SetConnectivityDirty( true );
  270. a->SetLastResolvedState( line );
  271. m_frame->AddToScreen( a, m_frame->GetScreen() );
  272. m_newDragLines.insert( a );
  273. SCH_LINE* b = new SCH_LINE( a->GetStartPoint(), line->GetLayer() );
  274. b->MoveStart( VECTOR2I( splitDelta.x, splitDelta.y ) );
  275. b->SetFlags( IS_NEW | STARTPOINT );
  276. b->SetConnectivityDirty( true );
  277. b->SetLastResolvedState( line );
  278. m_frame->AddToScreen( b, m_frame->GetScreen() );
  279. m_newDragLines.insert( b );
  280. xBendCount += yMoveBit;
  281. yBendCount += xMoveBit;
  282. // Ok move the unselected end of our item
  283. if( line->HasFlag( STARTPOINT ) )
  284. {
  285. line->MoveEnd( VECTOR2I( splitDelta.x ? splitDelta.x : xMove,
  286. splitDelta.y ? splitDelta.y : yMove ) );
  287. }
  288. else
  289. {
  290. line->MoveStart( VECTOR2I( splitDelta.x ? splitDelta.x : xMove,
  291. splitDelta.y ? splitDelta.y : yMove ) );
  292. }
  293. // Update our cache of the connected items. First, attach our drag labels to the line
  294. // left behind.
  295. for( EDA_ITEM* candidate : m_lineConnectionCache[line] )
  296. {
  297. SCH_LABEL_BASE* label = dynamic_cast<SCH_LABEL_BASE*>( candidate );
  298. if( label && m_specialCaseLabels.count( label ) )
  299. m_specialCaseLabels[label].attachedLine = a;
  300. }
  301. // We just broke off of the existing items, so replace all of them with our new end
  302. // connection.
  303. m_lineConnectionCache[a] = m_lineConnectionCache[line];
  304. m_lineConnectionCache[b].emplace_back( a );
  305. m_lineConnectionCache[line].clear();
  306. m_lineConnectionCache[line].emplace_back( b );
  307. }
  308. // Original line has no attachments, just move the unselected end
  309. else if( !foundAttachment )
  310. {
  311. if( line->HasFlag( STARTPOINT ) )
  312. line->MoveEnd( splitDelta );
  313. else
  314. line->MoveStart( splitDelta );
  315. }
  316. }
  317. }
  318. int SCH_MOVE_TOOL::Main( const TOOL_EVENT& aEvent )
  319. {
  320. m_isDrag = aEvent.IsAction( &SCH_ACTIONS::drag );
  321. if( SCH_COMMIT* commit = dynamic_cast<SCH_COMMIT*>( aEvent.Commit() ) )
  322. {
  323. bool isSlice = false;
  324. if( m_isDrag )
  325. isSlice = aEvent.Parameter<bool>();
  326. wxCHECK( aEvent.SynchronousState(), 0 );
  327. aEvent.SynchronousState()->store( STS_RUNNING );
  328. if( doMoveSelection( aEvent, commit, isSlice ) )
  329. aEvent.SynchronousState()->store( STS_FINISHED );
  330. else
  331. aEvent.SynchronousState()->store( STS_CANCELLED );
  332. }
  333. else
  334. {
  335. SCH_COMMIT localCommit( m_toolMgr );
  336. if( doMoveSelection( aEvent, &localCommit, false ) )
  337. localCommit.Push( m_isDrag ? _( "Drag" ) : _( "Move" ) );
  338. else
  339. localCommit.Revert();
  340. }
  341. return 0;
  342. }
  343. bool SCH_MOVE_TOOL::doMoveSelection( const TOOL_EVENT& aEvent, SCH_COMMIT* aCommit, bool aIsSlice )
  344. {
  345. SETTINGS_MANAGER& mgr = Pgm().GetSettingsManager();
  346. EESCHEMA_SETTINGS* cfg = mgr.GetAppSettings<EESCHEMA_SETTINGS>( "eeschema" );
  347. KIGFX::VIEW_CONTROLS* controls = getViewControls();
  348. EE_GRID_HELPER grid( m_toolMgr );
  349. bool wasDragging = m_moveInProgress && m_isDrag;
  350. m_anchorPos.reset();
  351. if( m_moveInProgress )
  352. {
  353. if( m_isDrag != wasDragging )
  354. {
  355. EDA_ITEM* sel = m_selectionTool->GetSelection().Front();
  356. if( sel && !sel->IsNew() )
  357. {
  358. // Reset the selected items so we can start again with the current m_isDrag
  359. // state.
  360. aCommit->Revert();
  361. m_selectionTool->RemoveItemsFromSel( &m_dragAdditions, QUIET_MODE );
  362. m_anchorPos = m_cursor - m_moveOffset;
  363. m_moveInProgress = false;
  364. controls->SetAutoPan( false );
  365. // And give it a kick so it doesn't have to wait for the first mouse movement
  366. // to refresh.
  367. m_toolMgr->PostAction( SCH_ACTIONS::restartMove );
  368. }
  369. }
  370. else
  371. {
  372. // The tool hotkey is interpreted as a click when already dragging/moving
  373. m_toolMgr->PostAction( ACTIONS::cursorClick );
  374. }
  375. return false;
  376. }
  377. if( m_inMoveTool ) // Must come after m_moveInProgress checks above...
  378. return false;
  379. REENTRANCY_GUARD guard( &m_inMoveTool );
  380. SCH_SELECTION& userSelection = m_selectionTool->GetSelection();
  381. // If a single pin is selected, promote the move selection to its parent symbol
  382. if( userSelection.GetSize() == 1 )
  383. {
  384. EDA_ITEM* selItem = userSelection.Front();
  385. if( selItem->Type() == SCH_PIN_T )
  386. {
  387. EDA_ITEM* parent = selItem->GetParent();
  388. if( parent->Type() == SCH_SYMBOL_T )
  389. {
  390. m_selectionTool->ClearSelection();
  391. m_selectionTool->AddItemToSel( parent );
  392. }
  393. }
  394. }
  395. // Be sure that there is at least one item that we can move. If there's no selection try
  396. // looking for the stuff under mouse cursor (i.e. Kicad old-style hover selection).
  397. SCH_SELECTION& selection = m_selectionTool->RequestSelection( SCH_COLLECTOR::MovableItems,
  398. true );
  399. bool unselect = selection.IsHover();
  400. // Keep an original copy of the starting points for cleanup after the move
  401. std::vector<DANGLING_END_ITEM> internalPoints;
  402. Activate();
  403. // Must be done after Activate() so that it gets set into the correct context
  404. controls->ShowCursor( true );
  405. m_frame->PushTool( aEvent );
  406. if( selection.Empty() )
  407. {
  408. // Note that it's important to go through push/pop even when the selection is empty.
  409. // This keeps other tools from having to special-case an empty move.
  410. m_frame->PopTool( aEvent );
  411. return false;
  412. }
  413. bool restore_state = false;
  414. TOOL_EVENT copy = aEvent;
  415. TOOL_EVENT* evt = &copy;
  416. VECTOR2I prevPos;
  417. GRID_HELPER_GRIDS snapLayer = GRID_CURRENT;
  418. m_cursor = controls->GetCursorPosition();
  419. // Main loop: keep receiving events
  420. do
  421. {
  422. m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::MOVING );
  423. grid.SetSnap( !evt->Modifier( MD_SHIFT ) );
  424. grid.SetUseGrid( getView()->GetGAL()->GetGridSnapping() && !evt->DisableGridSnapping() );
  425. if( evt->IsAction( &SCH_ACTIONS::restartMove )
  426. || evt->IsAction( &SCH_ACTIONS::move )
  427. || evt->IsAction( &SCH_ACTIONS::drag )
  428. || evt->IsMotion()
  429. || evt->IsDrag( BUT_LEFT )
  430. || evt->IsAction( &ACTIONS::refreshPreview ) )
  431. {
  432. if( !m_moveInProgress ) // Prepare to start moving/dragging
  433. {
  434. SCH_ITEM* sch_item = (SCH_ITEM*) selection.Front();
  435. bool placingNewItems = sch_item && sch_item->IsNew();
  436. //------------------------------------------------------------------------
  437. // Setup a drag or a move
  438. //
  439. m_dragAdditions.clear();
  440. m_specialCaseLabels.clear();
  441. m_specialCaseSheetPins.clear();
  442. internalPoints.clear();
  443. clearNewDragLines();
  444. for( SCH_ITEM* it : m_frame->GetScreen()->Items() )
  445. {
  446. it->ClearFlags( SELECTED_BY_DRAG );
  447. if( !it->IsSelected() )
  448. it->ClearFlags( STARTPOINT | ENDPOINT );
  449. }
  450. // Drag of split items start over top of their other segment so
  451. // we want to skip grabbing the segments we split from
  452. if( m_isDrag && !aIsSlice )
  453. {
  454. EDA_ITEMS connectedDragItems;
  455. // Add connections to the selection for a drag.
  456. // Do all non-labels/entries first so we don't add junctions to drag
  457. // when the line will eventually be drag selected.
  458. std::vector<SCH_ITEM*> stageTwo;
  459. for( EDA_ITEM* edaItem : selection )
  460. {
  461. SCH_ITEM* item = static_cast<SCH_ITEM*>( edaItem );
  462. std::vector<VECTOR2I> connections;
  463. switch( item->Type() )
  464. {
  465. case SCH_LABEL_T:
  466. case SCH_HIER_LABEL_T:
  467. case SCH_GLOBAL_LABEL_T:
  468. case SCH_DIRECTIVE_LABEL_T:
  469. stageTwo.emplace_back(item);
  470. break;
  471. case SCH_LINE_T:
  472. static_cast<SCH_LINE*>( item )->GetSelectedPoints( connections );
  473. break;
  474. default:
  475. connections = item->GetConnectionPoints();
  476. }
  477. for( const VECTOR2I& point : connections )
  478. getConnectedDragItems( aCommit, item, point, connectedDragItems );
  479. }
  480. // Go back and get all label connections now that we can test for drag-selected
  481. // lines the labels might be on
  482. for( SCH_ITEM* item : stageTwo )
  483. {
  484. for( const VECTOR2I& point : item->GetConnectionPoints() )
  485. getConnectedDragItems( aCommit, item, point, connectedDragItems );
  486. }
  487. for( EDA_ITEM* item : connectedDragItems )
  488. {
  489. m_dragAdditions.push_back( item->m_Uuid );
  490. m_selectionTool->AddItemToSel( item, QUIET_MODE );
  491. }
  492. // Pre-cache all connections of our selected objects so we can keep track of
  493. // what they were originally connected to as we drag them around
  494. for( EDA_ITEM* edaItem : selection )
  495. {
  496. SCH_ITEM* schItem = static_cast<SCH_ITEM*>( edaItem );
  497. if( schItem->Type() == SCH_LINE_T )
  498. {
  499. SCH_LINE* line = static_cast<SCH_LINE*>( schItem );
  500. //Also store the original angle of the line, is needed later to decide
  501. //which segment to extend when they've become zero length
  502. line->StoreAngle();
  503. for( const VECTOR2I& point : line->GetConnectionPoints() )
  504. getConnectedItems( line, point, m_lineConnectionCache[line] );
  505. }
  506. }
  507. }
  508. else
  509. {
  510. // Mark the edges of the block with dangling flags for a move.
  511. for( EDA_ITEM* item : selection )
  512. static_cast<SCH_ITEM*>( item )->GetEndPoints( internalPoints );
  513. std::vector<DANGLING_END_ITEM> endPointsByType = internalPoints;
  514. std::vector<DANGLING_END_ITEM> endPointsByPos = endPointsByType;
  515. DANGLING_END_ITEM_HELPER::sort_dangling_end_items( endPointsByType,
  516. endPointsByPos );
  517. for( EDA_ITEM* item : selection )
  518. static_cast<SCH_ITEM*>( item )->UpdateDanglingState( endPointsByType,
  519. endPointsByPos );
  520. }
  521. // Generic setup
  522. snapLayer = grid.GetSelectionGrid( selection );
  523. for( EDA_ITEM* item : selection )
  524. {
  525. if( item->IsNew() )
  526. {
  527. // Item was added to commit in a previous command
  528. }
  529. else if( item->GetParent() && item->GetParent()->IsSelected() )
  530. {
  531. // Item will be (or has been) added to commit by parent
  532. }
  533. else
  534. {
  535. aCommit->Modify( item, m_frame->GetScreen() );
  536. }
  537. item->SetFlags( IS_MOVING );
  538. if( SCH_ITEM* schItem = dynamic_cast<SCH_ITEM*>( item ) )
  539. schItem->SetStoredPos( schItem->GetPosition() );
  540. }
  541. // Set up the starting position and move/drag offset
  542. //
  543. m_cursor = controls->GetCursorPosition();
  544. if( evt->IsAction( &SCH_ACTIONS::restartMove ) )
  545. {
  546. wxASSERT_MSG( m_anchorPos, "Should be already set from previous cmd" );
  547. }
  548. else if( placingNewItems )
  549. {
  550. m_anchorPos = selection.GetReferencePoint();
  551. }
  552. if( m_anchorPos )
  553. {
  554. VECTOR2I delta = m_cursor - (*m_anchorPos);
  555. bool isPasted = false;
  556. // Drag items to the current cursor position
  557. for( EDA_ITEM* item : selection )
  558. {
  559. // Don't double move pins, fields, etc.
  560. if( item->GetParent() && item->GetParent()->IsSelected() )
  561. continue;
  562. moveItem( item, delta );
  563. updateItem( item, false );
  564. isPasted |= ( item->GetFlags() & IS_PASTED ) != 0;
  565. item->ClearFlags( IS_PASTED );
  566. }
  567. // The first time pasted items are moved we need to store the position of the
  568. // cursor so that rotate while moving works as expected (instead of around the
  569. // original anchor point
  570. if( isPasted )
  571. selection.SetReferencePoint( m_cursor );
  572. m_anchorPos = m_cursor;
  573. }
  574. // For some items, moving the cursor to anchor is not good (for instance large
  575. // hierarchical sheets or symbols can have the anchor outside the view)
  576. else if( selection.Size() == 1 && !sch_item->IsMovableFromAnchorPoint() )
  577. {
  578. m_cursor = getViewControls()->GetCursorPosition( true );
  579. m_anchorPos = m_cursor;
  580. }
  581. else
  582. {
  583. if( m_frame->GetMoveWarpsCursor() )
  584. {
  585. // User wants to warp the mouse
  586. m_cursor = grid.BestDragOrigin( m_cursor, snapLayer, selection );
  587. selection.SetReferencePoint( m_cursor );
  588. }
  589. else
  590. {
  591. // User does not want to warp the mouse
  592. m_cursor = getViewControls()->GetCursorPosition( true );
  593. }
  594. }
  595. controls->SetCursorPosition( m_cursor, false );
  596. prevPos = m_cursor;
  597. controls->SetAutoPan( true );
  598. m_moveInProgress = true;
  599. }
  600. //------------------------------------------------------------------------
  601. // Follow the mouse
  602. //
  603. m_cursor = grid.BestSnapAnchor( controls->GetCursorPosition( false ),
  604. snapLayer, selection );
  605. VECTOR2I delta( m_cursor - prevPos );
  606. m_anchorPos = m_cursor;
  607. // We need to check if the movement will change the net offset direction on the
  608. // X an Y axes. This is because we remerge added bend lines in realtime, and we
  609. // also account for the direction of the move when adding bend lines. So, if the
  610. // move direction changes, we need to split it into a move that gets us back to
  611. // zero, then the rest of the move.
  612. std::vector<VECTOR2I> splitMoves;
  613. if( alg::signbit( m_moveOffset.x ) != alg::signbit( ( m_moveOffset + delta ).x ) )
  614. {
  615. splitMoves.emplace_back( VECTOR2I( -1 * m_moveOffset.x, 0 ) );
  616. splitMoves.emplace_back( VECTOR2I( delta.x + m_moveOffset.x, 0 ) );
  617. }
  618. else
  619. {
  620. splitMoves.emplace_back( VECTOR2I( delta.x, 0 ) );
  621. }
  622. if( alg::signbit( m_moveOffset.y ) != alg::signbit( ( m_moveOffset + delta ).y ) )
  623. {
  624. splitMoves.emplace_back( VECTOR2I( 0, -1 * m_moveOffset.y ) );
  625. splitMoves.emplace_back( VECTOR2I( 0, delta.y + m_moveOffset.y ) );
  626. }
  627. else
  628. {
  629. splitMoves.emplace_back( VECTOR2I( 0, delta.y ) );
  630. }
  631. m_moveOffset += delta;
  632. prevPos = m_cursor;
  633. // Used for tracking how far off a drag end should have its 90 degree elbow added
  634. int xBendCount = 1;
  635. int yBendCount = 1;
  636. // Split the move into X and Y moves so we can correctly drag orthogonal lines
  637. for( const VECTOR2I& splitDelta : splitMoves )
  638. {
  639. // Skip non-moves
  640. if( splitDelta == VECTOR2I( 0, 0 ) )
  641. continue;
  642. for( EDA_ITEM* item : selection.GetItemsSortedByTypeAndXY( ( delta.x >= 0 ),
  643. ( delta.y >= 0 ) ) )
  644. {
  645. // Don't double move pins, fields, etc.
  646. if( item->GetParent() && item->GetParent()->IsSelected() )
  647. continue;
  648. SCH_LINE* line = dynamic_cast<SCH_LINE*>( item );
  649. // Only partially selected drag lines in orthogonal line mode need special
  650. // handling
  651. if( m_isDrag
  652. && cfg->m_Drawing.line_mode != LINE_MODE::LINE_MODE_FREE
  653. && line
  654. && line->HasFlag( STARTPOINT ) != line->HasFlag( ENDPOINT ) )
  655. {
  656. orthoLineDrag( aCommit, line, splitDelta, xBendCount, yBendCount, grid );
  657. }
  658. // Move all other items normally, including the selected end of partially
  659. // selected lines
  660. moveItem( item, splitDelta );
  661. updateItem( item, false );
  662. // Update any lines connected to sheet pins to the sheet pin's location
  663. // (which may not exactly follow the splitDelta as the pins are constrained
  664. // along the sheet edges.
  665. for( const auto& [pin, lineEnd] : m_specialCaseSheetPins )
  666. {
  667. if( lineEnd.second && lineEnd.first->HasFlag( STARTPOINT ) )
  668. lineEnd.first->SetStartPoint( pin->GetPosition() );
  669. else if( !lineEnd.second && lineEnd.first->HasFlag( ENDPOINT ) )
  670. lineEnd.first->SetEndPoint( pin->GetPosition() );
  671. }
  672. }
  673. }
  674. if( selection.HasReferencePoint() )
  675. selection.SetReferencePoint( selection.GetReferencePoint() + delta );
  676. m_toolMgr->PostEvent( EVENTS::SelectedItemsMoved );
  677. }
  678. //------------------------------------------------------------------------
  679. // Handle cancel
  680. //
  681. else if( evt->IsCancelInteractive()
  682. || evt->IsActivate()
  683. || evt->IsAction( &ACTIONS::undo ) )
  684. {
  685. if( evt->IsCancelInteractive() )
  686. m_frame->GetInfoBar()->Dismiss();
  687. if( m_moveInProgress )
  688. {
  689. if( evt->IsActivate() )
  690. {
  691. // Allowing other tools to activate during a move runs the risk of race
  692. // conditions in which we try to spool up both event loops at once.
  693. if( m_isDrag )
  694. m_frame->ShowInfoBarMsg( _( "Press <ESC> to cancel drag." ) );
  695. else
  696. m_frame->ShowInfoBarMsg( _( "Press <ESC> to cancel move." ) );
  697. evt->SetPassEvent( false );
  698. continue;
  699. }
  700. evt->SetPassEvent( false );
  701. restore_state = true;
  702. }
  703. clearNewDragLines();
  704. break;
  705. }
  706. //------------------------------------------------------------------------
  707. // Handle TOOL_ACTION special cases
  708. //
  709. else if( evt->IsAction( &ACTIONS::doDelete ) )
  710. {
  711. evt->SetPassEvent();
  712. // Exit on a delete; there will no longer be anything to drag.
  713. break;
  714. }
  715. else if( evt->IsAction( &ACTIONS::duplicate )
  716. || evt->IsAction( &SCH_ACTIONS::repeatDrawItem )
  717. || evt->IsAction( &ACTIONS::redo ) )
  718. {
  719. wxBell();
  720. }
  721. else if( evt->IsAction( &SCH_ACTIONS::rotateCW ) )
  722. {
  723. m_toolMgr->RunSynchronousAction( SCH_ACTIONS::rotateCW, aCommit );
  724. m_toolMgr->PostAction( ACTIONS::refreshPreview );
  725. }
  726. else if( evt->IsAction( &SCH_ACTIONS::rotateCCW ) )
  727. {
  728. m_toolMgr->RunSynchronousAction( SCH_ACTIONS::rotateCCW, aCommit );
  729. m_toolMgr->PostAction( ACTIONS::refreshPreview );
  730. }
  731. else if( evt->IsAction( &ACTIONS::increment ) )
  732. {
  733. m_toolMgr->RunSynchronousAction( ACTIONS::increment, aCommit, evt->Parameter<ACTIONS::INCREMENT>() );
  734. m_toolMgr->PostAction( ACTIONS::refreshPreview );
  735. }
  736. else if( evt->IsAction( &SCH_ACTIONS::toDLabel ) )
  737. {
  738. m_toolMgr->RunSynchronousAction(SCH_ACTIONS::toDLabel, aCommit );
  739. m_toolMgr->PostAction( ACTIONS::refreshPreview );
  740. }
  741. else if( evt->IsAction( &SCH_ACTIONS::toGLabel ) )
  742. {
  743. m_toolMgr->RunSynchronousAction( SCH_ACTIONS::toGLabel, aCommit );
  744. m_toolMgr->PostAction( ACTIONS::refreshPreview );
  745. }
  746. else if( evt->IsAction( &SCH_ACTIONS::toHLabel ) )
  747. {
  748. m_toolMgr->RunSynchronousAction( SCH_ACTIONS::toHLabel, aCommit );
  749. m_toolMgr->PostAction( ACTIONS::refreshPreview );
  750. }
  751. else if( evt->IsAction( &SCH_ACTIONS::toLabel ) )
  752. {
  753. m_toolMgr->RunSynchronousAction( SCH_ACTIONS::toLabel, aCommit );
  754. m_toolMgr->PostAction( ACTIONS::refreshPreview );
  755. }
  756. else if( evt->IsAction( &SCH_ACTIONS::toText ) )
  757. {
  758. m_toolMgr->RunSynchronousAction( SCH_ACTIONS::toText, aCommit );
  759. m_toolMgr->PostAction( ACTIONS::refreshPreview );
  760. }
  761. else if( evt->IsAction( &SCH_ACTIONS::toTextBox ) )
  762. {
  763. m_toolMgr->RunSynchronousAction( SCH_ACTIONS::toTextBox, aCommit );
  764. m_toolMgr->PostAction( ACTIONS::refreshPreview );
  765. }
  766. else if( evt->Action() == TA_CHOICE_MENU_CHOICE )
  767. {
  768. if( *evt->GetCommandId() >= ID_POPUP_SCH_SELECT_UNIT
  769. && *evt->GetCommandId() <= ID_POPUP_SCH_SELECT_UNIT_END )
  770. {
  771. SCH_SYMBOL* symbol = dynamic_cast<SCH_SYMBOL*>( selection.Front() );
  772. int unit = *evt->GetCommandId() - ID_POPUP_SCH_SELECT_UNIT;
  773. if( symbol )
  774. {
  775. m_frame->SelectUnit( symbol, unit );
  776. m_toolMgr->PostAction( ACTIONS::refreshPreview );
  777. }
  778. }
  779. else if( *evt->GetCommandId() >= ID_POPUP_SCH_SELECT_BASE
  780. && *evt->GetCommandId() <= ID_POPUP_SCH_SELECT_ALT )
  781. {
  782. SCH_SYMBOL* symbol = dynamic_cast<SCH_SYMBOL*>( selection.Front() );
  783. int bodyStyle = ( *evt->GetCommandId() - ID_POPUP_SCH_SELECT_BASE ) + 1;
  784. if( symbol && symbol->GetBodyStyle() != bodyStyle )
  785. {
  786. m_frame->FlipBodyStyle( symbol );
  787. m_toolMgr->PostAction( ACTIONS::refreshPreview );
  788. }
  789. }
  790. }
  791. else if( evt->IsAction( &SCH_ACTIONS::highlightNet )
  792. || evt->IsAction( &SCH_ACTIONS::selectOnPCB ) )
  793. {
  794. // These don't make any sense during a move. Eat them.
  795. }
  796. //------------------------------------------------------------------------
  797. // Handle context menu
  798. //
  799. else if( evt->IsClick( BUT_RIGHT ) )
  800. {
  801. m_menu->ShowContextMenu( m_selectionTool->GetSelection() );
  802. }
  803. //------------------------------------------------------------------------
  804. // Handle drop
  805. //
  806. else if( evt->IsMouseUp( BUT_LEFT )
  807. || evt->IsClick( BUT_LEFT )
  808. || evt->IsDblClick( BUT_LEFT ) )
  809. {
  810. break; // Finish
  811. }
  812. else
  813. {
  814. evt->SetPassEvent();
  815. }
  816. controls->SetAutoPan( m_moveInProgress );
  817. } while( ( evt = Wait() ) ); //Should be assignment not equality test
  818. // Create a selection of original selection, drag selected/changed items, and new
  819. // bend lines for later before we clear them in the aCommit. We'll need these
  820. // to check for new junctions needed, etc.
  821. SCH_SELECTION selectionCopy( selection );
  822. for( SCH_LINE* line : m_newDragLines )
  823. selectionCopy.Add( line );
  824. for( SCH_LINE* line : m_changedDragLines )
  825. selectionCopy.Add( line );
  826. // Save whatever new bend lines and changed lines survived the drag
  827. for( SCH_LINE* newLine : m_newDragLines )
  828. {
  829. newLine->ClearEditFlags();
  830. aCommit->Added( newLine, m_frame->GetScreen() );
  831. }
  832. // These lines have been changed, but aren't selected. We need
  833. // to manually clear these edit flags or they'll stick around.
  834. for( SCH_LINE* oldLine : m_changedDragLines )
  835. oldLine->ClearEditFlags();
  836. m_newDragLines.clear();
  837. m_changedDragLines.clear();
  838. controls->ForceCursorPosition( false );
  839. controls->ShowCursor( false );
  840. controls->SetAutoPan( false );
  841. m_moveOffset = { 0, 0 };
  842. m_anchorPos.reset();
  843. if( restore_state )
  844. {
  845. m_selectionTool->RemoveItemsFromSel( &m_dragAdditions, QUIET_MODE );
  846. }
  847. else
  848. {
  849. // One last update after exiting loop (for slower stuff, such as updating SCREEN's RTree).
  850. for( EDA_ITEM* item : selection )
  851. {
  852. updateItem( item, true );
  853. if( SCH_ITEM* sch_item = dynamic_cast<SCH_ITEM*>( item ) )
  854. sch_item->SetConnectivityDirty( true );
  855. }
  856. if( selection.GetSize() == 1 && selection.Front()->IsNew() )
  857. m_frame->SaveCopyForRepeatItem( static_cast<SCH_ITEM*>( selection.Front() ) );
  858. m_selectionTool->RemoveItemsFromSel( &m_dragAdditions, QUIET_MODE );
  859. // If we move items away from a junction, we _may_ want to add a junction there
  860. // to denote the state.
  861. for( const DANGLING_END_ITEM& it : internalPoints )
  862. {
  863. if( m_frame->GetScreen()->IsExplicitJunctionNeeded( it.GetPosition()) )
  864. m_frame->AddJunction( aCommit, m_frame->GetScreen(), it.GetPosition() );
  865. }
  866. SCH_LINE_WIRE_BUS_TOOL* lwbTool = m_toolMgr->GetTool<SCH_LINE_WIRE_BUS_TOOL>();
  867. lwbTool->TrimOverLappingWires( aCommit, &selectionCopy );
  868. lwbTool->AddJunctionsIfNeeded( aCommit, &selectionCopy );
  869. // This needs to run prior to `RecalculateConnections` because we need to identify
  870. // the lines that are newly dangling
  871. if( m_isDrag && !aIsSlice )
  872. trimDanglingLines( aCommit );
  873. // Auto-rotate any moved labels
  874. for( EDA_ITEM* item : selection )
  875. m_frame->AutoRotateItem( m_frame->GetScreen(), static_cast<SCH_ITEM*>( item ) );
  876. m_frame->Schematic().CleanUp( aCommit );
  877. }
  878. for( EDA_ITEM* item : m_frame->GetScreen()->Items() )
  879. item->ClearEditFlags();
  880. // ensure any selected item not in screen main list (for instance symbol fields)
  881. // has its edit flags cleared
  882. for( EDA_ITEM* item : selectionCopy )
  883. item->ClearEditFlags();
  884. if( unselect )
  885. m_toolMgr->RunAction( SCH_ACTIONS::clearSelection );
  886. else
  887. m_selectionTool->RebuildSelection(); // Schematic cleanup might have merged lines, etc.
  888. m_dragAdditions.clear();
  889. m_lineConnectionCache.clear();
  890. m_moveInProgress = false;
  891. m_frame->PopTool( aEvent );
  892. return !restore_state;
  893. }
  894. void SCH_MOVE_TOOL::trimDanglingLines( SCH_COMMIT* aCommit )
  895. {
  896. // Need a local cleanup first to ensure we remove unneeded junctions
  897. m_frame->Schematic().CleanUp( aCommit, m_frame->GetScreen() );
  898. std::set<SCH_ITEM*> danglers;
  899. std::function<void( SCH_ITEM* )> changeHandler =
  900. [&]( SCH_ITEM* aChangedItem ) -> void
  901. {
  902. m_toolMgr->GetView()->Update( aChangedItem, KIGFX::REPAINT );
  903. // Delete newly dangling lines:
  904. // Find split segments (one segment is new, the other is changed) that
  905. // we aren't dragging and don't have selected
  906. if( aChangedItem->HasFlag( IS_BROKEN) && aChangedItem->IsDangling()
  907. && !aChangedItem->IsSelected() )
  908. {
  909. danglers.insert( aChangedItem );
  910. }
  911. };
  912. m_frame->GetScreen()->TestDanglingEnds( nullptr, &changeHandler );
  913. for( SCH_ITEM* line : danglers )
  914. {
  915. line->SetFlags( STRUCT_DELETED );
  916. aCommit->Removed( line, m_frame->GetScreen() );
  917. updateItem( line, false );
  918. m_frame->RemoveFromScreen( line, m_frame->GetScreen() );
  919. }
  920. }
  921. void SCH_MOVE_TOOL::getConnectedItems( SCH_ITEM* aOriginalItem, const VECTOR2I& aPoint,
  922. EDA_ITEMS& aList )
  923. {
  924. EE_RTREE& items = m_frame->GetScreen()->Items();
  925. EE_RTREE::EE_TYPE itemsOverlapping = items.Overlapping( aOriginalItem->GetBoundingBox() );
  926. SCH_ITEM* foundJunction = nullptr;
  927. SCH_ITEM* foundSymbol = nullptr;
  928. // If you're connected to a junction, you're only connected to the junction.
  929. //
  930. // But, if you're connected to a junction on a pin, you're only connected to the pin. This
  931. // is because junctions and pins have different logic for how bend lines are generated and
  932. // we need to prioritize the pin version in some cases.
  933. for( SCH_ITEM* item : itemsOverlapping )
  934. {
  935. if( item != aOriginalItem && item->IsConnected( aPoint ) )
  936. {
  937. if( item->Type() == SCH_JUNCTION_T )
  938. foundJunction = item;
  939. else if( item->Type() == SCH_SYMBOL_T )
  940. foundSymbol = item;
  941. }
  942. }
  943. if( foundSymbol && foundJunction )
  944. {
  945. aList.push_back( foundSymbol );
  946. return;
  947. }
  948. if( foundJunction )
  949. {
  950. aList.push_back( foundJunction );
  951. return;
  952. }
  953. for( SCH_ITEM* test : itemsOverlapping )
  954. {
  955. if( test == aOriginalItem || !test->CanConnect( aOriginalItem ) )
  956. continue;
  957. switch( test->Type() )
  958. {
  959. case SCH_LINE_T:
  960. {
  961. SCH_LINE* line = static_cast<SCH_LINE*>( test );
  962. // When getting lines for the connection cache, it's important that we only add
  963. // items at the unselected end, since that is the only end that is handled specially.
  964. // Fully selected lines, and the selected end of a partially selected line, are moved
  965. // around normally and don't care about their connections.
  966. if( ( line->HasFlag( STARTPOINT ) && aPoint == line->GetStartPoint() )
  967. || ( line->HasFlag( ENDPOINT ) && aPoint == line->GetEndPoint() ) )
  968. {
  969. continue;
  970. }
  971. if( test->IsConnected( aPoint ) )
  972. aList.push_back( test );
  973. // Labels can connect to a wire (or bus) anywhere along the length
  974. if( SCH_LABEL_BASE* label = dynamic_cast<SCH_LABEL_BASE*>( aOriginalItem ) )
  975. {
  976. if( static_cast<SCH_LINE*>( test )->HitTest( label->GetPosition(), 1 ) )
  977. aList.push_back( test );
  978. }
  979. break;
  980. }
  981. case SCH_SHEET_T:
  982. if( aOriginalItem->Type() == SCH_LINE_T )
  983. {
  984. SCH_LINE* line = static_cast<SCH_LINE*>( aOriginalItem );
  985. for( SCH_SHEET_PIN* pin : static_cast<SCH_SHEET*>( test )->GetPins() )
  986. {
  987. if( pin->IsConnected( aPoint ) )
  988. {
  989. if( pin->IsSelected() )
  990. m_specialCaseSheetPins[pin] = { line, line->GetStartPoint() == aPoint };
  991. aList.push_back( pin );
  992. }
  993. }
  994. }
  995. break;
  996. case SCH_SYMBOL_T:
  997. case SCH_JUNCTION_T:
  998. case SCH_NO_CONNECT_T:
  999. if( test->IsConnected( aPoint ) )
  1000. aList.push_back( test );
  1001. break;
  1002. case SCH_LABEL_T:
  1003. case SCH_GLOBAL_LABEL_T:
  1004. case SCH_HIER_LABEL_T:
  1005. case SCH_DIRECTIVE_LABEL_T:
  1006. // Labels can connect to a wire (or bus) anywhere along the length
  1007. if( aOriginalItem->Type() == SCH_LINE_T && test->CanConnect( aOriginalItem ) )
  1008. {
  1009. SCH_LABEL_BASE* label = static_cast<SCH_LABEL_BASE*>( test );
  1010. SCH_LINE* line = static_cast<SCH_LINE*>( aOriginalItem );
  1011. if( line->HitTest( label->GetPosition(), 1 ) )
  1012. aList.push_back( label );
  1013. }
  1014. break;
  1015. case SCH_BUS_WIRE_ENTRY_T:
  1016. case SCH_BUS_BUS_ENTRY_T:
  1017. if( aOriginalItem->Type() == SCH_LINE_T && test->CanConnect( aOriginalItem ) )
  1018. {
  1019. SCH_TEXT* label = static_cast<SCH_TEXT*>( test );
  1020. SCH_LINE* line = static_cast<SCH_LINE*>( aOriginalItem );
  1021. if( line->HitTest( aPoint, 1 ) )
  1022. aList.push_back( label );
  1023. }
  1024. break;
  1025. default:
  1026. break;
  1027. }
  1028. }
  1029. }
  1030. void SCH_MOVE_TOOL::getConnectedDragItems( SCH_COMMIT* aCommit, SCH_ITEM* aSelectedItem,
  1031. const VECTOR2I& aPoint, EDA_ITEMS& aList )
  1032. {
  1033. EE_RTREE& items = m_frame->GetScreen()->Items();
  1034. EE_RTREE::EE_TYPE itemsOverlappingRTree = items.Overlapping( aSelectedItem->GetBoundingBox() );
  1035. std::vector<SCH_ITEM*> itemsConnectable;
  1036. bool ptHasUnselectedJunction = false;
  1037. auto makeNewWire =
  1038. [this]( SCH_COMMIT* commit, SCH_ITEM* fixed, SCH_ITEM* selected, const VECTOR2I& start,
  1039. const VECTOR2I& end )
  1040. {
  1041. SCH_LINE* newWire;
  1042. // Add a new newWire between the fixed item and the selected item so the selected
  1043. // item can be dragged.
  1044. if( fixed->GetLayer() == LAYER_BUS_JUNCTION || fixed->GetLayer() == LAYER_BUS
  1045. || selected->GetLayer() == LAYER_BUS )
  1046. {
  1047. newWire = new SCH_LINE( start, LAYER_BUS );
  1048. }
  1049. else
  1050. {
  1051. newWire = new SCH_LINE( start, LAYER_WIRE );
  1052. }
  1053. newWire->SetFlags( IS_NEW );
  1054. newWire->SetConnectivityDirty( true );
  1055. if( dynamic_cast<const SCH_LINE*>( selected ) )
  1056. newWire->SetLastResolvedState( selected );
  1057. else if( dynamic_cast<const SCH_LINE*>( fixed ) )
  1058. newWire->SetLastResolvedState( fixed );
  1059. newWire->SetEndPoint( end );
  1060. m_frame->AddToScreen( newWire, m_frame->GetScreen() );
  1061. commit->Added( newWire, m_frame->GetScreen() );
  1062. return newWire;
  1063. };
  1064. auto makeNewJunction =
  1065. [this]( SCH_COMMIT* commit, SCH_LINE* line, const VECTOR2I& pt )
  1066. {
  1067. SCH_JUNCTION* junction = new SCH_JUNCTION( pt );
  1068. junction->SetFlags( IS_NEW );
  1069. junction->SetConnectivityDirty( true );
  1070. junction->SetLastResolvedState( line );
  1071. if( line->IsBus() )
  1072. junction->SetLayer( LAYER_BUS_JUNCTION );
  1073. m_frame->AddToScreen( junction, m_frame->GetScreen() );
  1074. commit->Added( junction, m_frame->GetScreen() );
  1075. return junction;
  1076. };
  1077. for( SCH_ITEM* item : itemsOverlappingRTree )
  1078. {
  1079. if( item->Type() == SCH_SHEET_T )
  1080. {
  1081. SCH_SHEET* sheet = static_cast<SCH_SHEET*>( item );
  1082. for( SCH_SHEET_PIN* pin : sheet->GetPins() )
  1083. {
  1084. if( !pin->IsSelected()
  1085. && pin->GetPosition() == aSelectedItem->GetPosition()
  1086. && pin->CanConnect( aSelectedItem ) )
  1087. {
  1088. itemsConnectable.push_back( pin );
  1089. }
  1090. }
  1091. continue;
  1092. }
  1093. // Skip ourselves, skip already selected items (but not lines, they need both ends tested)
  1094. // and skip unconnectable items
  1095. if( item == aSelectedItem
  1096. || ( item->Type() != SCH_LINE_T && item->IsSelected() )
  1097. || !item->CanConnect( aSelectedItem ) )
  1098. {
  1099. continue;
  1100. }
  1101. itemsConnectable.push_back( item );
  1102. }
  1103. for( SCH_ITEM* item : itemsConnectable )
  1104. {
  1105. if( item->Type() == SCH_JUNCTION_T && item->IsConnected( aPoint ) && !item->IsSelected() )
  1106. {
  1107. ptHasUnselectedJunction = true;
  1108. break;
  1109. }
  1110. }
  1111. SCH_LINE* newWire = nullptr;
  1112. for( SCH_ITEM* test : itemsConnectable )
  1113. {
  1114. KICAD_T testType = test->Type();
  1115. switch( testType )
  1116. {
  1117. case SCH_LINE_T:
  1118. {
  1119. // Select the connected end of wires/bus connections that don't have an unselected
  1120. // junction isolating them from the drag
  1121. if( ptHasUnselectedJunction )
  1122. break;
  1123. SCH_LINE* line = static_cast<SCH_LINE*>( test );
  1124. if( line->GetStartPoint() == aPoint )
  1125. {
  1126. // It's possible to manually select one end of a line and get a drag
  1127. // connected other end, so we set the flag and then early exit the loop
  1128. // later if the other drag items like labels attached to the line have
  1129. // already been grabbed during the partial selection process.
  1130. line->SetFlags( STARTPOINT );
  1131. if( line->HasFlag( SELECTED ) || line->HasFlag( SELECTED_BY_DRAG ) )
  1132. {
  1133. continue;
  1134. }
  1135. else
  1136. {
  1137. line->SetFlags( SELECTED_BY_DRAG );
  1138. aList.push_back( line );
  1139. }
  1140. }
  1141. else if( line->GetEndPoint() == aPoint )
  1142. {
  1143. line->SetFlags( ENDPOINT );
  1144. if( line->HasFlag( SELECTED ) || line->HasFlag( SELECTED_BY_DRAG ) )
  1145. {
  1146. continue;
  1147. }
  1148. else
  1149. {
  1150. line->SetFlags( SELECTED_BY_DRAG );
  1151. aList.push_back( line );
  1152. }
  1153. }
  1154. else
  1155. {
  1156. switch( aSelectedItem->Type() )
  1157. {
  1158. // These items can connect anywhere along a line
  1159. case SCH_BUS_BUS_ENTRY_T:
  1160. case SCH_BUS_WIRE_ENTRY_T:
  1161. case SCH_LABEL_T:
  1162. case SCH_HIER_LABEL_T:
  1163. case SCH_GLOBAL_LABEL_T:
  1164. case SCH_DIRECTIVE_LABEL_T:
  1165. // Only add a line if this line is unselected; if the label and line are both
  1166. // selected they'll move together
  1167. if( line->HitTest( aPoint, 1 ) && !line->HasFlag( SELECTED )
  1168. && !line->HasFlag( SELECTED_BY_DRAG ) )
  1169. {
  1170. newWire = makeNewWire( aCommit, line, aSelectedItem, aPoint, aPoint );
  1171. newWire->SetFlags( SELECTED_BY_DRAG | STARTPOINT );
  1172. newWire->StoreAngle( ( line->Angle() + ANGLE_90 ).Normalize() );
  1173. aList.push_back( newWire );
  1174. if( aPoint != line->GetStartPoint() && aPoint != line->GetEndPoint() )
  1175. {
  1176. // Split line in half
  1177. if( !line->IsNew() )
  1178. aCommit->Modify( line, m_frame->GetScreen() );
  1179. VECTOR2I oldEnd = line->GetEndPoint();
  1180. line->SetEndPoint( aPoint );
  1181. makeNewWire( aCommit, line, line, aPoint, oldEnd );
  1182. makeNewJunction( aCommit, line, aPoint );
  1183. }
  1184. else
  1185. {
  1186. m_lineConnectionCache[ newWire ] = { line };
  1187. m_lineConnectionCache[ line ] = { newWire };
  1188. }
  1189. }
  1190. break;
  1191. default:
  1192. break;
  1193. }
  1194. break;
  1195. }
  1196. // Since only one end is going to move, the movement vector of any labels attached to
  1197. // it is scaled by the proportion of the line length the label is from the moving end.
  1198. for( SCH_ITEM* item : items.Overlapping( line->GetBoundingBox() ) )
  1199. {
  1200. SCH_LABEL_BASE* label = dynamic_cast<SCH_LABEL_BASE*>( item );
  1201. if( !label || label->IsSelected() )
  1202. continue; // These will be moved on their own because they're selected
  1203. if( label->HasFlag( SELECTED_BY_DRAG ) )
  1204. continue;
  1205. if( label->CanConnect( line ) && line->HitTest( label->GetPosition(), 1 ) )
  1206. {
  1207. label->SetFlags( SELECTED_BY_DRAG );
  1208. aList.push_back( label );
  1209. SPECIAL_CASE_LABEL_INFO info;
  1210. info.attachedLine = line;
  1211. info.originalLabelPos = label->GetPosition();
  1212. m_specialCaseLabels[label] = info;
  1213. }
  1214. }
  1215. break;
  1216. }
  1217. case SCH_SHEET_T:
  1218. for( SCH_SHEET_PIN* pin : static_cast<SCH_SHEET*>( test )->GetPins() )
  1219. {
  1220. if( pin->IsConnected( aPoint ) )
  1221. {
  1222. if( pin->IsSelected() && aSelectedItem->Type() == SCH_LINE_T )
  1223. {
  1224. SCH_LINE* line = static_cast<SCH_LINE*>( aSelectedItem );
  1225. m_specialCaseSheetPins[ pin ] = { line, line->GetStartPoint() == aPoint };
  1226. }
  1227. else if( !newWire )
  1228. {
  1229. // Add a new wire between the sheetpin and the selected item so the
  1230. // selected item can be dragged.
  1231. newWire = makeNewWire( aCommit, pin, aSelectedItem, aPoint, aPoint );
  1232. newWire->SetFlags( SELECTED_BY_DRAG | STARTPOINT );
  1233. aList.push_back( newWire );
  1234. }
  1235. }
  1236. }
  1237. break;
  1238. case SCH_SYMBOL_T:
  1239. case SCH_JUNCTION_T:
  1240. if( test->IsConnected( aPoint ) && !newWire )
  1241. {
  1242. // Add a new wire between the symbol or junction and the selected item so
  1243. // the selected item can be dragged.
  1244. newWire = makeNewWire( aCommit, test, aSelectedItem, aPoint, aPoint );
  1245. newWire->SetFlags( SELECTED_BY_DRAG | STARTPOINT );
  1246. aList.push_back( newWire );
  1247. }
  1248. break;
  1249. case SCH_NO_CONNECT_T:
  1250. // Select no-connects that are connected to items being moved.
  1251. if( !test->HasFlag( SELECTED_BY_DRAG ) && test->IsConnected( aPoint ) )
  1252. {
  1253. aList.push_back( test );
  1254. test->SetFlags( SELECTED_BY_DRAG );
  1255. }
  1256. break;
  1257. case SCH_LABEL_T:
  1258. case SCH_GLOBAL_LABEL_T:
  1259. case SCH_HIER_LABEL_T:
  1260. case SCH_DIRECTIVE_LABEL_T:
  1261. case SCH_SHEET_PIN_T:
  1262. // Performance optimization:
  1263. if( test->HasFlag( SELECTED_BY_DRAG ) )
  1264. break;
  1265. // Select labels that are connected to a wire (or bus) being moved.
  1266. if( aSelectedItem->Type() == SCH_LINE_T && test->CanConnect( aSelectedItem ) )
  1267. {
  1268. SCH_LABEL_BASE* label = static_cast<SCH_LABEL_BASE*>( test );
  1269. SCH_LINE* line = static_cast<SCH_LINE*>( aSelectedItem );
  1270. bool oneEndFixed = !line->HasFlag( STARTPOINT ) || !line->HasFlag( ENDPOINT );
  1271. if( line->HitTest( label->GetTextPos(), 1 ) )
  1272. {
  1273. if( ( !line->HasFlag( STARTPOINT ) && label->GetPosition() == line->GetStartPoint() )
  1274. || ( !line->HasFlag( ENDPOINT ) && label->GetPosition() == line->GetEndPoint() ) )
  1275. {
  1276. //If we have a line selected at only one end, don't grab labels
  1277. //connected directly to the unselected endpoint
  1278. break;
  1279. }
  1280. else
  1281. {
  1282. label->SetFlags( SELECTED_BY_DRAG );
  1283. aList.push_back( label );
  1284. if( oneEndFixed )
  1285. {
  1286. SPECIAL_CASE_LABEL_INFO info;
  1287. info.attachedLine = line;
  1288. info.originalLabelPos = label->GetPosition();
  1289. m_specialCaseLabels[label] = info;
  1290. }
  1291. }
  1292. }
  1293. }
  1294. else if( test->IsConnected( aPoint ) && !newWire )
  1295. {
  1296. // Add a new wire between the label and the selected item so the selected item
  1297. // can be dragged.
  1298. newWire = makeNewWire( aCommit, test, aSelectedItem, aPoint, aPoint );
  1299. newWire->SetFlags( SELECTED_BY_DRAG | STARTPOINT );
  1300. aList.push_back( newWire );
  1301. }
  1302. break;
  1303. case SCH_BUS_WIRE_ENTRY_T:
  1304. case SCH_BUS_BUS_ENTRY_T:
  1305. // Performance optimization:
  1306. if( test->HasFlag( SELECTED_BY_DRAG ) )
  1307. break;
  1308. // Select bus entries that are connected to a bus being moved.
  1309. if( aSelectedItem->Type() == SCH_LINE_T && test->CanConnect( aSelectedItem ) )
  1310. {
  1311. SCH_LINE* line = static_cast<SCH_LINE*>( aSelectedItem );
  1312. if( ( !line->HasFlag( STARTPOINT ) && test->IsConnected( line->GetStartPoint() ) )
  1313. || ( !line->HasFlag( ENDPOINT ) && test->IsConnected( line->GetEndPoint() ) ) )
  1314. {
  1315. // If we have a line selected at only one end, don't grab bus entries
  1316. // connected directly to the unselected endpoint
  1317. continue;
  1318. }
  1319. for( VECTOR2I& point : test->GetConnectionPoints() )
  1320. {
  1321. if( line->HitTest( point, 1 ) )
  1322. {
  1323. test->SetFlags( SELECTED_BY_DRAG );
  1324. aList.push_back( test );
  1325. // A bus entry needs its wire & label as well
  1326. std::vector<VECTOR2I> ends = test->GetConnectionPoints();
  1327. VECTOR2I otherEnd;
  1328. if( ends[0] == point )
  1329. otherEnd = ends[1];
  1330. else
  1331. otherEnd = ends[0];
  1332. getConnectedDragItems( aCommit, test, otherEnd, aList );
  1333. // No need to test the other end of the bus entry
  1334. break;
  1335. }
  1336. }
  1337. }
  1338. break;
  1339. default:
  1340. break;
  1341. }
  1342. }
  1343. }
  1344. void SCH_MOVE_TOOL::moveItem( EDA_ITEM* aItem, const VECTOR2I& aDelta )
  1345. {
  1346. switch( aItem->Type() )
  1347. {
  1348. case SCH_LINE_T:
  1349. {
  1350. SCH_LINE* line = static_cast<SCH_LINE*>( aItem );
  1351. if( aItem->HasFlag( STARTPOINT ) || !m_isDrag )
  1352. line->MoveStart( aDelta );
  1353. if( aItem->HasFlag( ENDPOINT ) || !m_isDrag )
  1354. line->MoveEnd( aDelta );
  1355. break;
  1356. }
  1357. case SCH_PIN_T:
  1358. case SCH_FIELD_T:
  1359. {
  1360. SCH_ITEM* parent = (SCH_ITEM*) aItem->GetParent();
  1361. VECTOR2I delta( aDelta );
  1362. if( parent && parent->Type() == SCH_SYMBOL_T )
  1363. {
  1364. SCH_SYMBOL* symbol = (SCH_SYMBOL*) aItem->GetParent();
  1365. TRANSFORM transform = symbol->GetTransform().InverseTransform();
  1366. delta = transform.TransformCoordinate( delta );
  1367. }
  1368. static_cast<SCH_ITEM*>( aItem )->Move( delta );
  1369. // If we're moving a field with respect to its parent then it's no longer auto-placed
  1370. if( aItem->Type() == SCH_FIELD_T && parent && !parent->IsSelected() )
  1371. parent->SetFieldsAutoplaced( AUTOPLACE_NONE );
  1372. break;
  1373. }
  1374. case SCH_SHEET_PIN_T:
  1375. {
  1376. SCH_SHEET_PIN* pin = (SCH_SHEET_PIN*) aItem;
  1377. pin->SetStoredPos( pin->GetStoredPos() + aDelta );
  1378. pin->ConstrainOnEdge( pin->GetStoredPos(), true );
  1379. break;
  1380. }
  1381. case SCH_LABEL_T:
  1382. case SCH_DIRECTIVE_LABEL_T:
  1383. case SCH_GLOBAL_LABEL_T:
  1384. case SCH_HIER_LABEL_T:
  1385. {
  1386. SCH_LABEL_BASE* label = static_cast<SCH_LABEL_BASE*>( aItem );
  1387. if( m_specialCaseLabels.count( label ) )
  1388. {
  1389. SPECIAL_CASE_LABEL_INFO info = m_specialCaseLabels[ label ];
  1390. SEG currentLine( info.attachedLine->GetStartPoint(), info.attachedLine->GetEndPoint() );
  1391. label->SetPosition( currentLine.NearestPoint( info.originalLabelPos ) );
  1392. }
  1393. else
  1394. {
  1395. label->Move( aDelta );
  1396. }
  1397. break;
  1398. }
  1399. default:
  1400. static_cast<SCH_ITEM*>( aItem )->Move( aDelta );
  1401. break;
  1402. }
  1403. aItem->SetFlags( IS_MOVING );
  1404. }
  1405. int SCH_MOVE_TOOL::AlignToGrid( const TOOL_EVENT& aEvent )
  1406. {
  1407. EE_GRID_HELPER grid( m_toolMgr);
  1408. SCH_SELECTION& selection = m_selectionTool->RequestSelection( SCH_COLLECTOR::MovableItems );
  1409. GRID_HELPER_GRIDS selectionGrid = grid.GetSelectionGrid( selection );
  1410. SCH_COMMIT commit( m_toolMgr );
  1411. auto doMoveItem =
  1412. [&]( EDA_ITEM* item, const VECTOR2I& delta )
  1413. {
  1414. commit.Modify( item, m_frame->GetScreen() );
  1415. // Ensure only one end is moved when calling moveItem
  1416. // i.e. we are in drag mode
  1417. bool tmp_isDrag = m_isDrag;
  1418. m_isDrag = true;
  1419. moveItem( item, delta );
  1420. m_isDrag = tmp_isDrag;
  1421. item->ClearFlags( IS_MOVING );
  1422. updateItem( item, true );
  1423. };
  1424. for( SCH_ITEM* it : m_frame->GetScreen()->Items() )
  1425. {
  1426. if( !it->IsSelected() )
  1427. it->ClearFlags( STARTPOINT | ENDPOINT );
  1428. if( !selection.IsHover() && it->IsSelected() )
  1429. it->SetFlags( STARTPOINT | ENDPOINT );
  1430. it->SetStoredPos( it->GetPosition() );
  1431. if( it->Type() == SCH_SHEET_T )
  1432. {
  1433. for( SCH_SHEET_PIN* pin : static_cast<SCH_SHEET*>( it )->GetPins() )
  1434. pin->SetStoredPos( pin->GetPosition() );
  1435. }
  1436. }
  1437. for( EDA_ITEM* item : selection )
  1438. {
  1439. if( item->Type() == SCH_LINE_T )
  1440. {
  1441. SCH_LINE* line = static_cast<SCH_LINE*>( item );
  1442. std::vector<int> flags{ STARTPOINT, ENDPOINT };
  1443. std::vector<VECTOR2I> pts{ line->GetStartPoint(), line->GetEndPoint() };
  1444. for( int ii = 0; ii < 2; ++ii )
  1445. {
  1446. EDA_ITEMS drag_items{ item };
  1447. line->ClearFlags();
  1448. line->SetFlags( SELECTED );
  1449. line->SetFlags( flags[ii] );
  1450. getConnectedDragItems( &commit, line, pts[ii], drag_items );
  1451. std::set<EDA_ITEM*> unique_items( drag_items.begin(), drag_items.end() );
  1452. VECTOR2I delta = grid.AlignGrid( pts[ii], selectionGrid ) - pts[ii];
  1453. if( delta != VECTOR2I( 0, 0 ) )
  1454. {
  1455. for( EDA_ITEM* dragItem : unique_items )
  1456. {
  1457. if( dragItem->GetParent() && dragItem->GetParent()->IsSelected() )
  1458. continue;
  1459. doMoveItem( dragItem, delta );
  1460. }
  1461. }
  1462. }
  1463. }
  1464. else if( item->Type() == SCH_FIELD_T || item->Type() == SCH_TEXT_T )
  1465. {
  1466. VECTOR2I delta = grid.AlignGrid( item->GetPosition(), selectionGrid ) - item->GetPosition();
  1467. if( delta != VECTOR2I( 0, 0 ) )
  1468. doMoveItem( item, delta );
  1469. }
  1470. else if( item->Type() == SCH_SHEET_T )
  1471. {
  1472. SCH_SHEET* sheet = static_cast<SCH_SHEET*>( item );
  1473. VECTOR2I topLeft = sheet->GetPosition();
  1474. VECTOR2I bottomRight = topLeft + sheet->GetSize();
  1475. VECTOR2I tl_delta = grid.AlignGrid( topLeft, selectionGrid ) - topLeft;
  1476. VECTOR2I br_delta = grid.AlignGrid( bottomRight, selectionGrid ) - bottomRight;
  1477. if( tl_delta != VECTOR2I( 0, 0 ) || br_delta != VECTOR2I( 0, 0 ) )
  1478. {
  1479. doMoveItem( sheet, tl_delta );
  1480. VECTOR2I newSize = (VECTOR2I) sheet->GetSize() - tl_delta + br_delta;
  1481. sheet->SetSize( VECTOR2I( newSize.x, newSize.y ) );
  1482. updateItem( sheet, true );
  1483. }
  1484. for( SCH_SHEET_PIN* pin : sheet->GetPins() )
  1485. {
  1486. VECTOR2I newPos;
  1487. if( pin->GetSide() == SHEET_SIDE::TOP || pin->GetSide() == SHEET_SIDE::LEFT )
  1488. newPos = pin->GetPosition() + tl_delta;
  1489. else
  1490. newPos = pin->GetPosition() + br_delta;
  1491. VECTOR2I delta = grid.AlignGrid( newPos - pin->GetPosition(), selectionGrid );
  1492. if( delta != VECTOR2I( 0, 0 ) )
  1493. {
  1494. EDA_ITEMS drag_items;
  1495. getConnectedDragItems( &commit, pin, pin->GetConnectionPoints()[0],
  1496. drag_items );
  1497. doMoveItem( pin, delta );
  1498. for( EDA_ITEM* dragItem : drag_items )
  1499. {
  1500. if( dragItem->GetParent() && dragItem->GetParent()->IsSelected() )
  1501. continue;
  1502. doMoveItem( dragItem, delta );
  1503. }
  1504. }
  1505. }
  1506. }
  1507. else
  1508. {
  1509. SCH_ITEM* schItem = static_cast<SCH_ITEM*>( item );
  1510. std::vector<VECTOR2I> connections = schItem->GetConnectionPoints();
  1511. EDA_ITEMS drag_items;
  1512. for( const VECTOR2I& point : connections )
  1513. getConnectedDragItems( &commit, schItem, point, drag_items );
  1514. std::map<VECTOR2I, int> shifts;
  1515. VECTOR2I most_common( 0, 0 );
  1516. int max_count = 0;
  1517. for( const VECTOR2I& conn : connections )
  1518. {
  1519. VECTOR2I gridpt = grid.AlignGrid( conn, selectionGrid ) - conn;
  1520. shifts[gridpt]++;
  1521. if( shifts[gridpt] > max_count )
  1522. {
  1523. most_common = gridpt;
  1524. max_count = shifts[most_common];
  1525. }
  1526. }
  1527. if( most_common != VECTOR2I( 0, 0 ) )
  1528. {
  1529. doMoveItem( item, most_common );
  1530. for( EDA_ITEM* dragItem : drag_items )
  1531. {
  1532. if( dragItem->GetParent() && dragItem->GetParent()->IsSelected() )
  1533. continue;
  1534. doMoveItem( dragItem, most_common );
  1535. }
  1536. }
  1537. }
  1538. }
  1539. SCH_LINE_WIRE_BUS_TOOL* lwbTool = m_toolMgr->GetTool<SCH_LINE_WIRE_BUS_TOOL>();
  1540. lwbTool->TrimOverLappingWires( &commit, &selection );
  1541. lwbTool->AddJunctionsIfNeeded( &commit, &selection );
  1542. m_toolMgr->PostEvent( EVENTS::SelectedItemsMoved );
  1543. m_frame->Schematic().CleanUp( &commit );
  1544. commit.Push( _( "Align Items to Grid" ) );
  1545. return 0;
  1546. }
  1547. void SCH_MOVE_TOOL::clearNewDragLines()
  1548. {
  1549. // Remove new bend lines added during the drag
  1550. for( SCH_LINE* newLine : m_newDragLines )
  1551. {
  1552. m_frame->RemoveFromScreen( newLine, m_frame->GetScreen() );
  1553. delete newLine;
  1554. }
  1555. m_newDragLines.clear();
  1556. }
  1557. void SCH_MOVE_TOOL::setTransitions()
  1558. {
  1559. Go( &SCH_MOVE_TOOL::Main, SCH_ACTIONS::move.MakeEvent() );
  1560. Go( &SCH_MOVE_TOOL::Main, SCH_ACTIONS::drag.MakeEvent() );
  1561. Go( &SCH_MOVE_TOOL::AlignToGrid, SCH_ACTIONS::alignToGrid.MakeEvent() );
  1562. }