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.

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