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.

1730 lines
64 KiB

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