KiCad PCB EDA Suite
Loading...
Searching...
No Matches
sch_move_tool.cpp
Go to the documentation of this file.
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, see <https://www.gnu.org/licenses/>.
19 */
20
21#include <advanced_config.h>
22#include <sch_shape.h>
23#include <algorithm>
24#include <cmath>
25#include <memory>
26#include <optional>
27#include <set>
28#include <wx/log.h>
29#include <wx/utils.h>
30#include <trigo.h>
32#include <tool/tool_manager.h>
37#include <tools/sch_move_tool.h>
38
39#include <sch_actions.h>
40#include <sch_commit.h>
41#include <eda_item.h>
42#include <sch_group.h>
43#include <sch_item.h>
44#include <sch_symbol.h>
45#include <sch_sheet.h>
46#include <sch_sheet_pin.h>
47#include <sch_line.h>
48#include <sch_connection.h>
49#include <sch_junction.h>
50#include <junction_helpers.h>
51#include <sch_edit_frame.h>
52#include <widgets/wx_infobar.h>
53#include <eeschema_id.h>
54#include <pgm_base.h>
55#include <view/view_controls.h>
57#include <math/box2.h>
58#include <base_units.h>
59#include <sch_screen.h>
60#include <sch_item_alignment.h>
61#include <trace_helpers.h>
62
63
64// For adding to or removing from selections
65#define QUIET_MODE true
66
67
68static bool isGraphicItemForDrop( const SCH_ITEM* aItem )
69{
70 switch( aItem->Type() )
71 {
72 case SCH_SHAPE_T:
73 case SCH_BITMAP_T:
74 case SCH_TEXT_T:
75 case SCH_TEXTBOX_T:
76 return true;
77 case SCH_LINE_T:
78 return static_cast<const SCH_LINE*>( aItem )->IsGraphicLine();
79 default:
80 return false;
81 }
82}
83
84
85// Distance of a point along a sheet border, clockwise from the top-left corner.
86static long long sheetBorderArc( const SCH_SHEET* aSheet, SHEET_SIDE aSide, const VECTOR2I& aPos )
87{
88 long long left = aSheet->GetPosition().x;
89 long long top = aSheet->GetPosition().y;
90 long long w = aSheet->GetSize().x;
91 long long h = aSheet->GetSize().y;
92 long long right = left + w;
93 long long bot = top + h;
94
95 switch( aSide )
96 {
97 case SHEET_SIDE::TOP: return aPos.x - left;
98 case SHEET_SIDE::RIGHT: return w + ( aPos.y - top );
99 case SHEET_SIDE::BOTTOM: return w + h + ( right - aPos.x );
100 case SHEET_SIDE::LEFT: return 2 * w + h + ( bot - aPos.y );
101 default: return 0;
102 }
103}
104
105
106// Inverse of sheetBorderArc(), turns a border distance back into a point and edge.
107static void sheetBorderPos( const SCH_SHEET* aSheet, long long aArc, VECTOR2I& aPos, SHEET_SIDE& aSide )
108{
109 long long left = aSheet->GetPosition().x;
110 long long top = aSheet->GetPosition().y;
111 long long w = aSheet->GetSize().x;
112 long long h = aSheet->GetSize().y;
113 long long right = left + w;
114 long long bot = top + h;
115 long long perimeter = 2 * ( w + h );
116
117 if( perimeter <= 0 )
118 {
119 aPos = VECTOR2I( (int) left, (int) top );
120 aSide = SHEET_SIDE::TOP;
121 return;
122 }
123
124 aArc = ( ( aArc % perimeter ) + perimeter ) % perimeter;
125
126 if( aArc <= w )
127 {
128 aSide = SHEET_SIDE::TOP;
129 aPos = VECTOR2I( (int) ( left + aArc ), (int) top );
130 }
131 else if( aArc <= w + h )
132 {
133 aSide = SHEET_SIDE::RIGHT;
134 aPos = VECTOR2I( (int) right, (int) ( top + ( aArc - w ) ) );
135 }
136 else if( aArc <= 2 * w + h )
137 {
138 aSide = SHEET_SIDE::BOTTOM;
139 aPos = VECTOR2I( (int) ( right - ( aArc - w - h ) ), (int) bot );
140 }
141 else
142 {
143 aSide = SHEET_SIDE::LEFT;
144 aPos = VECTOR2I( (int) left, (int) ( bot - ( aArc - 2 * w - h ) ) );
145 }
146}
147
148
149static void cloneWireConnection( SCH_LINE* aNewLine, SCH_ITEM* aSource, SCH_EDIT_FRAME* aFrame )
150{
151 if( !aNewLine || !aSource || !aFrame )
152 return;
153
154 SCH_LINE* sourceLine = dynamic_cast<SCH_LINE*>( aSource );
155
156 if( !sourceLine )
157 return;
158
159 SCH_SHEET_PATH sheetPath = aFrame->GetCurrentSheet();
160 SCH_CONNECTION* sourceConnection = sourceLine->Connection( &sheetPath );
161
162 if( !sourceConnection )
163 return;
164
165 SCH_CONNECTION* newConnection = aNewLine->InitializeConnection( sheetPath, nullptr );
166
167 if( !newConnection )
168 return;
169
170 newConnection->Clone( *sourceConnection );
171}
172
173
175 SCH_TOOL_BASE<SCH_EDIT_FRAME>( "eeschema.InteractiveMove" ),
176 m_inMoveTool( false ),
177 m_moveInProgress( false ),
178 m_mode( MOVE ),
179 m_moveOffset( 0, 0 )
180{
181}
182
183
185{
187
188 auto moveCondition =
189 []( const SELECTION& aSel )
190 {
191 if( aSel.Empty() || SELECTION_CONDITIONS::OnlyTypes( { SCH_MARKER_T } )( aSel ) )
192 return false;
193
195 return false;
196
197 return true;
198 };
199
200 // Add move actions to the selection tool menu
201 //
202 CONDITIONAL_MENU& selToolMenu = m_selectionTool->GetToolMenu().GetMenu();
203
204 selToolMenu.AddItem( SCH_ACTIONS::move, moveCondition, 150 );
205 selToolMenu.AddItem( SCH_ACTIONS::drag, moveCondition, 150 );
206 selToolMenu.AddItem( SCH_ACTIONS::alignToGrid, moveCondition, 150 );
207
208 return true;
209}
210
211
213{
214 SCH_TOOL_BASE::Reset( aReason );
215
216 if( aReason == MODEL_RELOAD || aReason == SUPERMODEL_RELOAD )
217 {
218 // If we were in the middle of a move/drag operation and the model changes (e.g., sheet
219 // switch), we need to clean up our state to avoid blocking future move/drag operations
220 if( m_moveInProgress )
221 {
222 // Clear the move state
223 m_moveInProgress = false;
224 m_mode = MOVE;
225 m_moveOffset = VECTOR2I( 0, 0 );
226 m_anchorPos.reset();
227 m_breakPos.reset();
228
229 // Clear cached data that references items from the previous sheet
230 m_dragAdditions.clear();
231 m_lineConnectionCache.clear();
232 m_newDragLines.clear();
233 m_changedDragLines.clear();
234 m_specialCaseLabels.clear();
236 m_sheetPinDragArc.clear();
237 m_hiddenJunctions.clear();
238
239 // Clear any preview
240 if( m_view )
241 m_view->ClearPreview();
242 }
243 }
244}
245
246
247void SCH_MOVE_TOOL::orthoLineDrag( SCH_COMMIT* aCommit, SCH_LINE* line, const VECTOR2I& splitDelta,
248 int& xBendCount, int& yBendCount, const EE_GRID_HELPER& grid )
249{
250 // If the move is not the same angle as this move, then we need to do something special with
251 // the unselected end to maintain orthogonality. Either drag some connected line that is the
252 // same angle as the move or add two lines to make a 90 degree connection
253 if( !EDA_ANGLE( splitDelta ).IsParallelTo( line->Angle() ) || line->GetLength() == 0 )
254 {
255 VECTOR2I unselectedEnd = line->HasFlag( STARTPOINT ) ? line->GetEndPoint()
256 : line->GetStartPoint();
257 VECTOR2I selectedEnd = line->HasFlag( STARTPOINT ) ? line->GetStartPoint()
258 : line->GetEndPoint();
259
260 // Look for pre-existing lines we can drag with us instead of creating new ones
261 bool foundAttachment = false;
262 bool foundJunction = false;
263 bool foundPin = false;
264 SCH_LINE* foundLine = nullptr;
265
266 for( EDA_ITEM* cItem : m_lineConnectionCache[line] )
267 {
268 foundAttachment = true;
269
270 // If the move is the same angle as a connected line, we can shrink/extend that line
271 // endpoint
272 switch( cItem->Type() )
273 {
274 case SCH_LINE_T:
275 {
276 SCH_LINE* cLine = static_cast<SCH_LINE*>( cItem );
277
278 // A matching angle on a non-zero-length line means lengthen/shorten will work
279 if( EDA_ANGLE( splitDelta ).IsParallelTo( cLine->Angle() )
280 && cLine->GetLength() != 0 )
281 {
282 foundLine = cLine;
283 }
284
285 // Zero length lines are lines that this algorithm has shortened to 0 so they also
286 // work but we should prefer using a segment with length and angle matching when
287 // we can (otherwise the zero length line will draw overlapping segments on them)
288 if( !foundLine && cLine->GetLength() == 0 )
289 foundLine = cLine;
290
291 break;
292 }
293 case SCH_JUNCTION_T:
294 foundJunction = true;
295 break;
296
297 case SCH_PIN_T:
298 foundPin = true;
299 break;
300
301 case SCH_SHEET_T:
302 for( const auto& pair : m_specialCaseSheetPins )
303 {
304 if( pair.first->IsConnected( selectedEnd ) )
305 {
306 foundPin = true;
307 break;
308 }
309 }
310
311 break;
312
313 default:
314 break;
315 }
316 }
317
318 // Ok... what if our original line is length zero from moving in its direction, and the
319 // last added segment of the 90 bend we are connected to is zero from moving it in its
320 // direction after it was added?
321 //
322 // If we are moving in original direction, we should lengthen the original drag wire.
323 // Otherwise we should lengthen the new wire.
324 bool preferOriginalLine = false;
325
326 if( foundLine
327 && foundLine->GetLength() == 0
328 && line->GetLength() == 0
329 && EDA_ANGLE( splitDelta ).IsParallelTo( line->GetStoredAngle() ) )
330 {
331 preferOriginalLine = true;
332 }
333 // If we have found an attachment, but not a line, we want to check if it's a junction.
334 // These are special-cased and get a single line added instead of a 90-degree bend. Except
335 // when we're on a pin, because pins always need bends, and junctions are just added to
336 // pins for visual clarity.
337 else if( !foundLine && foundJunction && !foundPin )
338 {
339 // Create a new wire ending at the unselected end
340 foundLine = new SCH_LINE( unselectedEnd, line->GetLayer() );
341 foundLine->SetFlags( IS_NEW );
342 foundLine->SetLastResolvedState( line );
343 cloneWireConnection( foundLine, line, m_frame );
344 m_frame->AddToScreen( foundLine, m_frame->GetScreen() );
345 m_newDragLines.insert( foundLine );
346
347 // We just broke off of the existing items, so replace all of them with our new
348 // end connection.
350 m_lineConnectionCache[line].clear();
351 m_lineConnectionCache[line].emplace_back( foundLine );
352 }
353
354 // We want to drag our found line if it's in the same angle as the move or zero length,
355 // but if the original drag line is also zero and the same original angle we should extend
356 // that one first
357 if( foundLine && !preferOriginalLine )
358 {
359 // Move the connected line found oriented in the direction of our move.
360 //
361 // Make sure we grab the right endpoint, it's not always STARTPOINT since the user can
362 // draw a box of lines. We need to only move one though, and preferably the start point,
363 // in case we have a zero length line that we are extending (we want the foundLine
364 // start point to be attached to the unselected end of our drag line).
365 //
366 // Also, new lines are added already so they'll be in the undo list, skip adding them.
367
368 if( !foundLine->HasFlag( IS_CHANGED ) && !foundLine->HasFlag( IS_NEW ) )
369 {
370 aCommit->Modify( (SCH_ITEM*) foundLine, m_frame->GetScreen() );
371
372 if( !foundLine->IsSelected() )
373 m_changedDragLines.insert( foundLine );
374 }
375
376 if( foundLine->GetStartPoint() == unselectedEnd )
377 foundLine->MoveStart( splitDelta );
378 else if( foundLine->GetEndPoint() == unselectedEnd )
379 foundLine->MoveEnd( splitDelta );
380
381 updateItem( foundLine, true );
382
383 SCH_LINE* bendLine = nullptr;
384
385 if( m_lineConnectionCache.count( foundLine ) == 1
386 && m_lineConnectionCache[foundLine][0]->Type() == SCH_LINE_T )
387 {
388 bendLine = static_cast<SCH_LINE*>( m_lineConnectionCache[foundLine][0] );
389 }
390
391 // Remerge segments we've created if this is a segment that we've added whose only
392 // other connection is also an added segment
393 //
394 // bendLine is first added segment at the original attachment point, foundLine is the
395 // orthogonal line between bendLine and this line
396 if( foundLine->HasFlag( IS_NEW )
397 && foundLine->GetLength() == 0
398 && bendLine && bendLine->HasFlag( IS_NEW ) )
399 {
400 if( line->HasFlag( STARTPOINT ) )
401 line->SetEndPoint( bendLine->GetEndPoint() );
402 else
403 line->SetStartPoint( bendLine->GetEndPoint() );
404
405 // Update our cache of the connected items.
406
407 // Re-attach drag labels from lines being deleted to the surviving line.
408 // This prevents dangling pointers when bendLine/foundLine are deleted below.
409 for( auto& [label, info] : m_specialCaseLabels )
410 {
411 if( info.attachedLine == bendLine || info.attachedLine == foundLine )
412 {
413 info.attachedLine = line;
414 info.originalLineStart = line->GetStartPoint();
415 info.originalLineEnd = line->GetEndPoint();
416 }
417 }
418
420 m_lineConnectionCache[bendLine].clear();
421 m_lineConnectionCache[foundLine].clear();
422
423 m_frame->RemoveFromScreen( bendLine, m_frame->GetScreen() );
424 m_frame->RemoveFromScreen( foundLine, m_frame->GetScreen() );
425
426 m_newDragLines.erase( bendLine );
427 m_newDragLines.erase( foundLine );
428
429 delete bendLine;
430 delete foundLine;
431 }
432 //Ok, move the unselected end of our item
433 else
434 {
435 if( line->HasFlag( STARTPOINT ) )
436 line->MoveEnd( splitDelta );
437 else
438 line->MoveStart( splitDelta );
439 }
440
441 updateItem( line, true );
442 }
443 else if( line->GetLength() == 0 )
444 {
445 // We didn't find another line to shorten/lengthen, (or we did but it's also zero)
446 // so now is a good time to use our existing zero-length original line
447 }
448 // Either no line was at the "right" angle, or this was a junction, pin, sheet, etc. We
449 // need to add segments to keep the soon-to-move unselected end connected to these items.
450 //
451 // To keep our drag selections all the same, we'll move our unselected end point and then
452 // put wires between it and its original endpoint.
453 else if( foundAttachment && line->IsOrthogonal() )
454 {
455 VECTOR2D lineGrid = grid.GetGridSize( grid.GetItemGrid( line ) );
456
457 // The bend counter handles a group of wires all needing their offset one grid movement
458 // further out from each other to not overlap. The absolute value stuff finds the
459 // direction of the line and hence the the bend increment on that axis
460 unsigned int xMoveBit = splitDelta.x != 0;
461 unsigned int yMoveBit = splitDelta.y != 0;
462 int xLength = abs( unselectedEnd.x - selectedEnd.x );
463 int yLength = abs( unselectedEnd.y - selectedEnd.y );
464 int xMove = ( xLength - ( xBendCount * lineGrid.x ) )
465 * sign( selectedEnd.x - unselectedEnd.x );
466 int yMove = ( yLength - ( yBendCount * lineGrid.y ) )
467 * sign( selectedEnd.y - unselectedEnd.y );
468
469 // Create a new wire ending at the unselected end, we'll move the new wire's start
470 // point to the unselected end
471 SCH_LINE* a = new SCH_LINE( unselectedEnd, line->GetLayer() );
472 a->MoveStart( VECTOR2I( xMove, yMove ) );
473 a->SetFlags( IS_NEW );
474 a->SetConnectivityDirty( true );
475 a->SetLastResolvedState( line );
476 cloneWireConnection( a, line, m_frame );
477 m_frame->AddToScreen( a, m_frame->GetScreen() );
478 m_newDragLines.insert( a );
479
480 SCH_LINE* b = new SCH_LINE( a->GetStartPoint(), line->GetLayer() );
481 b->MoveStart( VECTOR2I( splitDelta.x, splitDelta.y ) );
482 b->SetFlags( IS_NEW | STARTPOINT );
483 b->SetConnectivityDirty( true );
484 b->SetLastResolvedState( line );
485 cloneWireConnection( b, line, m_frame );
486 m_frame->AddToScreen( b, m_frame->GetScreen() );
487 m_newDragLines.insert( b );
488
489 xBendCount += yMoveBit;
490 yBendCount += xMoveBit;
491
492 // Ok move the unselected end of our item
493 if( line->HasFlag( STARTPOINT ) )
494 {
495 line->MoveEnd( VECTOR2I( splitDelta.x ? splitDelta.x : xMove,
496 splitDelta.y ? splitDelta.y : yMove ) );
497 }
498 else
499 {
500 line->MoveStart( VECTOR2I( splitDelta.x ? splitDelta.x : xMove,
501 splitDelta.y ? splitDelta.y : yMove ) );
502 }
503
504 // Update our cache of the connected items. First, attach our drag labels to the line
505 // left behind.
506 for( EDA_ITEM* candidate : m_lineConnectionCache[line] )
507 {
508 SCH_LABEL_BASE* label = dynamic_cast<SCH_LABEL_BASE*>( candidate );
509
510 if( !label || !m_specialCaseLabels.count( label ) )
511 continue;
512
513 if( label->GetPosition() == selectedEnd )
514 {
515 m_specialCaseLabels[label].trackMovingEnd = true;
516 }
517 else
518 {
519 m_specialCaseLabels[label].attachedLine = a;
520 m_specialCaseLabels[label].originalLineStart = a->GetStartPoint();
521 m_specialCaseLabels[label].originalLineEnd = a->GetEndPoint();
522 }
523 }
524
525 // We just broke off of the existing items, so replace all of them with our new end
526 // connection.
528 m_lineConnectionCache[b].emplace_back( a );
529 m_lineConnectionCache[line].clear();
530 m_lineConnectionCache[line].emplace_back( b );
531 }
532 // Original line has no attachments, just move the unselected end
533 else if( !foundAttachment )
534 {
535 if( line->HasFlag( STARTPOINT ) )
536 line->MoveEnd( splitDelta );
537 else
538 line->MoveStart( splitDelta );
539 }
540 }
541}
542
543
544int SCH_MOVE_TOOL::Main( const TOOL_EVENT& aEvent )
545{
546 MOVE_MODE mode = MOVE;
547
548 if( aEvent.IsAction( &SCH_ACTIONS::drag ) )
549 mode = DRAG;
550 else if( aEvent.IsAction( &SCH_ACTIONS::breakWire ) )
551 mode = BREAK;
552 else if( aEvent.IsAction( &SCH_ACTIONS::slice ) )
553 mode = SLICE;
554
555 bool requestedModeIsDragLike = mode != MOVE;
556 bool currentModeIsDragLike = m_mode != MOVE;
557
558 if( m_moveInProgress )
559 {
560 if( requestedModeIsDragLike != currentModeIsDragLike )
561 {
562 EDA_ITEM* item = m_selectionTool->GetSelection().Front();
563
564 if( item && !item->IsNew() )
565 m_toolMgr->PostAction( SCH_ACTIONS::restartMove, mode );
566 }
567 else
568 {
569 // Repeating the tool hotkey places the moving items.
570 m_toolMgr->PostAction( ACTIONS::cursorClick );
571 }
572
573 return 0;
574 }
575
576 if( m_inMoveTool )
577 return 0;
578
579 m_mode = mode;
580
581 if( SCH_COMMIT* commit = dynamic_cast<SCH_COMMIT*>( aEvent.Commit() ) )
582 {
583 wxCHECK( aEvent.SynchronousState(), 0 );
584 aEvent.SynchronousState()->store( STS_RUNNING );
585
586 if( doMoveSelection( aEvent, commit ) )
587 aEvent.SynchronousState()->store( STS_FINISHED );
588 else
589 aEvent.SynchronousState()->store( STS_CANCELLED );
590 }
591 else
592 {
593 SCH_COMMIT localCommit( m_toolMgr );
594
595 if( doMoveSelection( aEvent, &localCommit ) )
596 {
597 switch( m_mode )
598 {
599 case MOVE: localCommit.Push( _( "Move" ) ); break;
600 case DRAG: localCommit.Push( _( "Drag" ) ); break;
601 case BREAK: localCommit.Push( _( "Break Wire" ) ); break;
602 case SLICE: localCommit.Push( _( "Slice Wire" ) ); break;
603 }
604 }
605 else
606 {
607 localCommit.Revert();
608 }
609 }
610
611 return 0;
612}
613
614
616{
617 if( m_mode != BREAK && m_mode != SLICE )
618 return;
619
620 if( !aCommit )
621 return;
622
624
625 if( !lwbTool )
626 return;
627
628 SCH_SELECTION& selection = m_selectionTool->GetSelection();
629
630 if( selection.Empty() )
631 return;
632
633 std::vector<SCH_LINE*> lines;
634
635 for( EDA_ITEM* item : selection )
636 {
637 if( item->Type() == SCH_LINE_T )
638 {
639 // This function gets called every time segments are broken, which can also be for subsequent
640 // breaks in a loop without leaving the current move tool.
641 // Skip already placed segments (segment keeps IS_BROKEN but will have IS_NEW cleared below)
642 // so that only the actively placed tail segment gets split again.
643 if( item->HasFlag( IS_BROKEN ) && !item->HasFlag( IS_NEW ) )
644 continue;
645
646 lines.push_back( static_cast<SCH_LINE*>( item ) );
647 }
648 }
649
650 if( lines.empty() )
651 return;
652
654 SCH_SCREEN* screen = m_frame->GetScreen();
655 VECTOR2I cursorPos = controls->GetCursorPosition( !aEvent.DisableGridSnapping() );
656
657 bool useCursorForSingleLine = false;
658
659 if( lines.size() == 1 )
660 useCursorForSingleLine = true;
661
662 m_selectionTool->ClearSelection();
663 m_breakPos.reset();
664
665 for( SCH_LINE* line : lines )
666 {
667 VECTOR2I breakPos = useCursorForSingleLine ? cursorPos : line->GetMidPoint();
668
669 if( m_mode == BREAK && !m_breakPos )
670 m_breakPos = breakPos;
671
672 SCH_LINE* newLine = nullptr;
673
674 lwbTool->BreakSegment( aCommit, line, breakPos, &newLine, screen );
675
676 if( !newLine )
677 continue;
678
679 // If this is a second+ round break, we need to get rid of the IS_NEW flag since the new segment
680 // is now an existing segment we are breaking from, this will be checked for in the line selection
681 // gathering above
682 line->ClearFlags( STARTPOINT | IS_NEW );
683 line->SetFlags( ENDPOINT );
684 m_selectionTool->AddItemToSel( line );
685
686 newLine->ClearFlags( ENDPOINT | STARTPOINT );
687
688 if( m_mode == BREAK )
689 {
690 m_selectionTool->AddItemToSel( newLine );
691 newLine->SetFlags( STARTPOINT );
692 }
693 }
694}
695
696
698{
701 bool didAtLeastOneBreak = false;
702
703 m_anchorPos.reset();
704
706
707 preprocessBreakOrSliceSelection( aCommit, aEvent );
708
709 // Prepare selection (promote pins to symbols, request selection)
710 bool unselect = false;
711 SCH_SELECTION& selection = prepareSelection( unselect );
712
713 // Keep an original copy of the starting points for cleanup after the move
714 std::vector<DANGLING_END_ITEM> internalPoints;
715
716 // Track selection characteristics
717 bool selectionHasSheetPins = false;
718 bool selectionHasGraphicItems = false;
719 bool selectionHasNonGraphicItems = false;
720 bool selectionIsGraphicsOnly = false;
721
722 std::unique_ptr<SCH_DRAG_NET_COLLISION_MONITOR> netCollisionMonitor;
723
724 auto refreshTraits =
725 [&]()
726 {
727 refreshSelectionTraits( selection, selectionHasSheetPins, selectionHasGraphicItems,
728 selectionHasNonGraphicItems, selectionIsGraphicsOnly );
729 };
730
731 refreshTraits();
732
733 const bool graphicsOnly = ADVANCED_CFG::GetCfg().m_ConnectivityEngine
734 && std::all_of( selection.begin(), selection.end(),
735 []( EDA_ITEM* item )
736 {
737 return isGraphicItemForDrop( static_cast<SCH_ITEM*>( item ) );
738 } );
739
740 if( !selection.Empty() && !graphicsOnly )
741 {
742 netCollisionMonitor = std::make_unique<SCH_DRAG_NET_COLLISION_MONITOR>( m_frame, m_view );
743 netCollisionMonitor->Initialize( selection );
744 }
745
746 bool lastCtrlDown = false;
747
748 // When items are pasted via Ctrl+V, the Ctrl key is still held when the move tool
749 // starts. Ctrl disables grid snapping, so the pasted items would track off-grid.
750 // The synthetic move action event carries no modifier bits, so query the live
751 // keyboard state and ignore Ctrl until the user releases and re-presses it.
752 bool pasteHoldingCtrl = false;
753
754 for( EDA_ITEM* item : selection )
755 {
756 if( item->HasFlag( IS_PASTED ) )
757 {
758 pasteHoldingCtrl = wxGetKeyState( WXK_CONTROL );
759 break;
760 }
761 }
762
763 Activate();
764
765 // Must be done after Activate() so that it gets set into the correct context
766 controls->ShowCursor( true );
767
768 SCOPED_TOOL_PUSHER raii( m_frame, aEvent );
769
770 // Note that it's important to go through push/pop even when the selection is empty.
771 // This keeps other tools from having to special-case an empty move.
772 if( selection.Empty() )
773 return false;
774
775 bool restore_state = false;
776 TOOL_EVENT copy = aEvent;
777 TOOL_EVENT* evt = &copy;
778 VECTOR2I prevPos = controls->GetCursorPosition();
780 SCH_SHEET* hoverSheet = nullptr;
781 KICURSOR currentCursor = KICURSOR::MOVING;
782 m_cursor = controls->GetCursorPosition();
783
784 // Axis locking for arrow key movement
785 enum class AXIS_LOCK { NONE, HORIZONTAL, VERTICAL };
786 AXIS_LOCK axisLock = AXIS_LOCK::NONE;
787 long lastArrowKeyAction = 0;
788
789 // Main loop: keep receiving events
790 do
791 {
792 wxLogTrace( traceSchMove, "doMoveSelection: event loop iteration, evt=%s, action=%s",
793 evt->Category() == TC_MOUSE ? "MOUSE" :
794 evt->Category() == TC_KEYBOARD ? "KEYBOARD" :
795 evt->Category() == TC_COMMAND ? "COMMAND" : "OTHER",
796 evt->Format().c_str() );
797
798 m_frame->GetCanvas()->SetCurrentCursor( currentCursor );
799 grid.SetSnap( !evt->Modifier( MD_SHIFT ) );
800
801 bool ctrlDown = evt->Modifier( MD_CTRL );
802
803 // Only real input events carry modifier state; the synthetic move action does not.
804 bool hasModifierState = evt->Category() == TC_MOUSE || evt->Category() == TC_KEYBOARD;
805
806 if( pasteHoldingCtrl && hasModifierState && !ctrlDown )
807 pasteHoldingCtrl = false;
808
809 // The paste-held Ctrl only masks grid snapping. Ctrl also forces a graphics-only drop
810 // into a sheet, and that gesture must still honor a physically-held key.
811 bool gridSnapDisabled = ctrlDown && !pasteHoldingCtrl;
812
813 grid.SetUseGrid( getView()->GetGAL()->GetGridSnapping() && !gridSnapDisabled );
814
815 lastCtrlDown = ctrlDown;
816
818 || evt->IsAction( &SCH_ACTIONS::move )
819 || evt->IsAction( &SCH_ACTIONS::drag )
820 || evt->IsMotion()
821 || evt->IsDrag( BUT_LEFT )
823 {
824 refreshTraits();
825
827 {
828 restartMoveOperation( *evt, selection, aCommit, internalPoints, snapLayer );
829 prevPos = m_cursor;
830 refreshTraits();
831 }
832 else if( !m_moveInProgress ) // Prepare to start moving/dragging
833 {
834 startMoveOperation( selection, aCommit, internalPoints, snapLayer );
835 prevPos = m_cursor;
836 refreshTraits();
837 }
838
839 //------------------------------------------------------------------------
840 // Follow the mouse
841 //
842 m_view->ClearPreview();
843
844 // We need to bypass refreshPreview action here because it is triggered by the move, so we were
845 // getting double-key events that toggled the axis locking if you pressed them in a certain order.
847 {
848 VECTOR2I keyboardPos( controls->GetSettings().m_lastKeyboardCursorPosition );
849 long action = controls->GetSettings().m_lastKeyboardCursorCommand;
850
851 grid.SetSnap( false );
852 m_cursor = grid.Align( keyboardPos, snapLayer );
853
854 // Update axis lock based on arrow key press
855 if( action == ACTIONS::CURSOR_LEFT || action == ACTIONS::CURSOR_RIGHT )
856 {
857 if( axisLock == AXIS_LOCK::HORIZONTAL )
858 {
859 // Check if opposite horizontal key pressed to unlock
860 if( ( lastArrowKeyAction == ACTIONS::CURSOR_LEFT && action == ACTIONS::CURSOR_RIGHT ) ||
861 ( lastArrowKeyAction == ACTIONS::CURSOR_RIGHT && action == ACTIONS::CURSOR_LEFT ) )
862 {
863 axisLock = AXIS_LOCK::NONE;
864 }
865 // Same direction axis, keep locked
866 }
867 else
868 {
869 axisLock = AXIS_LOCK::HORIZONTAL;
870 }
871 }
872 else if( action == ACTIONS::CURSOR_UP || action == ACTIONS::CURSOR_DOWN )
873 {
874 if( axisLock == AXIS_LOCK::VERTICAL )
875 {
876 // Check if opposite vertical key pressed to unlock
877 if( ( lastArrowKeyAction == ACTIONS::CURSOR_UP && action == ACTIONS::CURSOR_DOWN ) ||
878 ( lastArrowKeyAction == ACTIONS::CURSOR_DOWN && action == ACTIONS::CURSOR_UP ) )
879 {
880 axisLock = AXIS_LOCK::NONE;
881 }
882 // Same direction axis, keep locked
883 }
884 else
885 {
886 axisLock = AXIS_LOCK::VERTICAL;
887 }
888 }
889
890 lastArrowKeyAction = action;
891 }
892 else
893 {
894 m_cursor = grid.ResolveSnap( controls->GetCursorPosition( false ), snapLayer, selection, prevPos )
895 .position;
896 }
897
898 if( axisLock == AXIS_LOCK::HORIZONTAL )
899 m_cursor.y = prevPos.y;
900 else if( axisLock == AXIS_LOCK::VERTICAL )
901 m_cursor.x = prevPos.x;
902
903 // Find potential target sheet for dropping. This relocation is only meaningful for a
904 // plain move; drag/break/slice reshape existing connections in place and must never
905 // pull items onto a sub-sheet's screen.
906 SCH_SHEET* sheet = nullptr;
907
908 if( m_mode == MOVE )
909 sheet = findTargetSheet( selection, m_cursor, selectionHasSheetPins, selectionIsGraphicsOnly,
910 ctrlDown );
911
912 if( sheet != hoverSheet )
913 {
914 if( hoverSheet )
915 {
916 hoverSheet->ClearFlags( BRIGHTENED );
917 m_frame->UpdateItem( hoverSheet, false );
918 }
919
920 hoverSheet = sheet;
921
922 if( hoverSheet )
923 {
924 hoverSheet->SetFlags( BRIGHTENED );
925 m_frame->UpdateItem( hoverSheet, false );
926 }
927 }
928
929 currentCursor = hoverSheet ? KICURSOR::PLACE : KICURSOR::MOVING;
930
931 if( netCollisionMonitor )
932 currentCursor = netCollisionMonitor->AdjustCursor( currentCursor );
933
934 VECTOR2I delta( m_cursor - prevPos );
936
937 // Used for tracking how far off a drag end should have its 90 degree elbow added
938 int xBendCount = 1;
939 int yBendCount = 1;
940
941 performItemMove( selection, delta, aCommit, xBendCount, yBendCount, grid );
942 prevPos = m_cursor;
943
944 std::vector<SCH_ITEM*> previewItems;
945
946 for( EDA_ITEM* it : selection )
947 previewItems.push_back( static_cast<SCH_ITEM*>( it ) );
948
949 for( SCH_LINE* line : m_newDragLines )
950 previewItems.push_back( line );
951
952 for( SCH_LINE* line : m_changedDragLines )
953 previewItems.push_back( line );
954
955 std::vector<SCH_JUNCTION*> previewJunctions =
956 JUNCTION_HELPERS::PreviewJunctions( m_frame->GetScreen(), previewItems );
957
958 if( netCollisionMonitor )
959 netCollisionMonitor->Update( previewJunctions, selection );
960
961 for( SCH_JUNCTION* jct : previewJunctions )
962 m_view->AddToPreview( jct, true );
963
965 }
966
967 //------------------------------------------------------------------------
968 // Handle cancel
969 //
970 else if( evt->IsCancelInteractive()
971 || evt->IsActivate()
972 || evt->IsAction( &ACTIONS::undo ) )
973 {
974 if( evt->IsCancelInteractive() )
975 {
976 m_frame->GetInfoBar()->Dismiss();
977
978 // When breaking, the user can cancel after multiple breaks to keep all but the last
979 // break, so exit normally if we have done at least one break
980 if( didAtLeastOneBreak && m_mode == BREAK )
981 break;
982 }
983
984 if( m_moveInProgress )
985 {
986 if( evt->IsActivate() )
987 {
988 // Allowing other tools to activate during a move runs the risk of race
989 // conditions in which we try to spool up both event loops at once.
990
991 switch( m_mode )
992 {
993 case MOVE: m_frame->ShowInfoBarMsg( _( "Press <ESC> to cancel move." ) ); break;
994 case DRAG: m_frame->ShowInfoBarMsg( _( "Press <ESC> to cancel drag." ) ); break;
995 case BREAK: m_frame->ShowInfoBarMsg( _( "Press <ESC> to cancel break." ) ); break;
996 case SLICE: m_frame->ShowInfoBarMsg( _( "Press <ESC> to cancel slice." ) ); break;
997 }
998
999 evt->SetPassEvent( false );
1000 continue;
1001 }
1002
1003 evt->SetPassEvent( false );
1004 restore_state = true;
1005 }
1006 else if( m_mode == BREAK || m_mode == SLICE )
1007 {
1008 // preprocessBreakOrSliceSelection() split the wire before any motion arrived,
1009 // so cancel must roll those edits back. Activations still pass through so the
1010 // requested tool starts.
1011 if( !evt->IsActivate() )
1012 evt->SetPassEvent( false );
1013
1014 restore_state = true;
1015 }
1016
1018
1019 m_view->ClearPreview();
1020
1021 break;
1022 }
1023 //------------------------------------------------------------------------
1024 // Handle TOOL_ACTION special cases
1025 //
1026 else if( !handleMoveToolActions( evt, aCommit, selection ) )
1027 {
1028 wxLogTrace( traceSchMove, "doMoveSelection: handleMoveToolActions returned false, exiting" );
1029 break; // Exit if told to by handler
1030 }
1031 //------------------------------------------------------------------------
1032 // Handle context menu
1033 //
1034 else if( evt->IsClick( BUT_RIGHT ) )
1035 {
1036 m_menu->ShowContextMenu( m_selectionTool->GetSelection() );
1037 }
1038 //------------------------------------------------------------------------
1039 // Handle drop
1040 //
1041 else if( evt->IsMouseUp( BUT_LEFT )
1042 || evt->IsClick( BUT_LEFT )
1043 || evt->IsAction( &ACTIONS::cursorClick ) )
1044 {
1045 if( m_mode != BREAK )
1046 {
1047 break; // Finish
1048 }
1049 else
1050 {
1051 didAtLeastOneBreak = true;
1052 preprocessBreakOrSliceSelection( aCommit, *evt );
1053 selection = m_selectionTool->RequestSelection( SCH_COLLECTOR::MovableItems, true );
1054
1055 if( m_breakPos )
1056 {
1059 selection.SetReferencePoint( m_cursor );
1060 m_moveOffset = VECTOR2I( 0, 0 );
1061 m_breakPos.reset();
1062
1063 controls->SetCursorPosition( m_cursor, false );
1064 prevPos = m_cursor;
1065 }
1066 }
1067 }
1068 else if( evt->IsDblClick( BUT_LEFT )
1069 || evt->IsAction( &ACTIONS::cursorDblClick ) )
1070 {
1071 // Double click always finishes, even breaks
1072 break;
1073 }
1074 // Don't call SetPassEvent() for events we've handled - let them be consumed
1075 else if( evt->IsAction( &SCH_ACTIONS::rotateCW )
1077 || evt->IsAction( &ACTIONS::increment )
1081 || evt->IsAction( &SCH_ACTIONS::toLabel )
1082 || evt->IsAction( &SCH_ACTIONS::toText )
1086 || evt->IsAction( &ACTIONS::duplicate )
1088 || evt->IsAction( &ACTIONS::redo ) )
1089 {
1090 // Event was already handled by handleMoveToolActions, don't pass it on
1091 wxLogTrace( traceSchMove, "doMoveSelection: event handled, not passing" );
1092 }
1093 else
1094 {
1095 evt->SetPassEvent();
1096 }
1097
1098 controls->SetAutoPan( m_moveInProgress );
1099
1100 } while( ( evt = Wait() ) ); //Should be assignment not equality test
1101
1102 SCH_SHEET* targetSheet = hoverSheet;
1103
1104 if( selectionHasSheetPins || ( selectionIsGraphicsOnly && !lastCtrlDown ) )
1105 targetSheet = nullptr;
1106
1107 if( hoverSheet )
1108 {
1109 hoverSheet->ClearFlags( BRIGHTENED );
1110 m_frame->UpdateItem( hoverSheet, false );
1111 }
1112
1113 if( restore_state )
1114 {
1115 for( const HIDDEN_JUNCTION& hidden : m_hiddenJunctions )
1116 m_view->Hide( hidden.m_junction, false );
1117
1118 m_selectionTool->RemoveItemsFromSel( &m_dragAdditions, QUIET_MODE );
1119
1120 // Clear the split-segment selection that preprocessBreakOrSliceSelection() built
1121 // before the caller's Revert() runs. Revert() rebuilds selection from the screen,
1122 // so leaving the splits selected keeps the restored wire hidden until the next
1123 // selection refresh.
1124 if( m_mode == BREAK || m_mode == SLICE )
1125 m_toolMgr->RunAction( ACTIONS::selectionClear );
1126 }
1127 else
1128 {
1129 // Only drop into a sheet when the move is committed, not when canceled.
1130 if( targetSheet )
1131 {
1132 moveSelectionToSheet( selection, targetSheet, aCommit );
1133 m_toolMgr->RunAction( ACTIONS::selectionClear );
1134 m_newDragLines.clear();
1135 m_changedDragLines.clear();
1136 }
1137
1138 finalizeMoveOperation( selection, aCommit, unselect, internalPoints );
1139 }
1140
1141 m_dragAdditions.clear();
1142 m_lineConnectionCache.clear();
1143 m_moveInProgress = false;
1144 m_breakPos.reset();
1145
1146 m_hiddenJunctions.clear();
1147 m_view->ClearPreview();
1148
1149 return !restore_state;
1150}
1151
1152
1154{
1155 SCH_SELECTION& userSelection = m_selectionTool->GetSelection();
1156
1157 // If a single pin is selected, promote the move selection to its parent symbol
1158 if( userSelection.GetSize() == 1 )
1159 {
1160 EDA_ITEM* selItem = userSelection.Front();
1161
1162 if( selItem->Type() == SCH_PIN_T )
1163 {
1164 EDA_ITEM* parent = selItem->GetParent();
1165
1166 if( parent->Type() == SCH_SYMBOL_T )
1167 {
1168 m_selectionTool->ClearSelection();
1169 m_selectionTool->AddItemToSel( parent );
1170 }
1171 }
1172 }
1173
1174 // Be sure that there is at least one item that we can move. If there's no selection try
1175 // looking for the stuff under mouse cursor (i.e. KiCad old-style hover selection).
1176 SCH_SELECTION& selection = m_selectionTool->RequestSelection( SCH_COLLECTOR::MovableItems, true );
1177 aUnselect = selection.IsHover();
1178
1179 m_selectionTool->FilterSelectionForLockedItems();
1180
1181 return selection;
1182}
1183
1184
1185void SCH_MOVE_TOOL::refreshSelectionTraits( const SCH_SELECTION& aSelection, bool& aHasSheetPins,
1186 bool& aHasGraphicItems, bool& aHasNonGraphicItems,
1187 bool& aIsGraphicsOnly )
1188{
1189 aHasSheetPins = false;
1190 aHasGraphicItems = false;
1191 aHasNonGraphicItems = false;
1192
1193 for( EDA_ITEM* edaItem : aSelection )
1194 {
1195 SCH_ITEM* schItem = static_cast<SCH_ITEM*>( edaItem );
1196
1197 if( schItem->Type() == SCH_SHEET_PIN_T )
1198 aHasSheetPins = true;
1199
1200 if( isGraphicItemForDrop( schItem ) )
1201 aHasGraphicItems = true;
1202 else if( schItem->Type() != SCH_SHEET_T )
1203 aHasNonGraphicItems = true;
1204 }
1205
1206 aIsGraphicsOnly = aHasGraphicItems && !aHasNonGraphicItems;
1207}
1208
1209
1211{
1212 // Drag of split items start over top of their other segment, so we want to skip grabbing
1213 // the segments we split from
1214 if( m_mode != DRAG && m_mode != BREAK )
1215 return;
1216
1217 EDA_ITEMS connectedDragItems;
1218
1219 // Add connections to the selection for a drag.
1220 // Do all non-labels/entries first so we don't add junctions to drag when the line will
1221 // eventually be drag selected.
1222 std::vector<SCH_ITEM*> stageTwo;
1223
1224 for( EDA_ITEM* edaItem : aSelection )
1225 {
1226 SCH_ITEM* item = static_cast<SCH_ITEM*>( edaItem );
1227 std::vector<VECTOR2I> connections;
1228
1229 switch( item->Type() )
1230 {
1231 case SCH_LABEL_T:
1232 case SCH_HIER_LABEL_T:
1233 case SCH_GLOBAL_LABEL_T:
1235 stageTwo.emplace_back( item );
1236 break;
1237
1238 case SCH_LINE_T:
1239 static_cast<SCH_LINE*>( item )->GetSelectedPoints( connections );
1240 break;
1241
1242 default:
1243 connections = item->GetConnectionPoints();
1244 }
1245
1246 for( const VECTOR2I& point : connections )
1247 getConnectedDragItems( aCommit, item, point, connectedDragItems );
1248 }
1249
1250 // Go back and get all label connections now that we can test for drag-selected lines
1251 // the labels might be on
1252 for( SCH_ITEM* item : stageTwo )
1253 {
1254 for( const VECTOR2I& point : item->GetConnectionPoints() )
1255 getConnectedDragItems( aCommit, item, point, connectedDragItems );
1256 }
1257
1258 for( EDA_ITEM* item : connectedDragItems )
1259 {
1260 m_dragAdditions.push_back( item->m_Uuid );
1261 m_selectionTool->AddItemToSel( item, QUIET_MODE );
1262 }
1263
1264 // Pre-cache all connections of our selected objects so we can keep track of what they
1265 // were originally connected to as we drag them around
1266 for( EDA_ITEM* edaItem : aSelection )
1267 {
1268 SCH_ITEM* schItem = static_cast<SCH_ITEM*>( edaItem );
1269
1270 if( schItem->Type() == SCH_LINE_T )
1271 {
1272 SCH_LINE* line = static_cast<SCH_LINE*>( schItem );
1273
1274 // Store the original angle of the line; needed later to decide which segment
1275 // to extend when they've become zero length
1276 line->StoreAngle();
1277
1278 for( const VECTOR2I& point : line->GetConnectionPoints() )
1279 getConnectedItems( line, point, m_lineConnectionCache[line] );
1280 }
1281 }
1282}
1283
1284
1285void SCH_MOVE_TOOL::setupItemsForMove( SCH_SELECTION& aSelection, std::vector<DANGLING_END_ITEM>& aInternalPoints )
1286{
1287 // Mark the edges of the block with dangling flags for a move
1288 for( EDA_ITEM* item : aSelection )
1289 static_cast<SCH_ITEM*>( item )->GetEndPoints( aInternalPoints );
1290
1291 std::vector<DANGLING_END_ITEM> endPointsByType = aInternalPoints;
1292 std::vector<DANGLING_END_ITEM> endPointsByPos = endPointsByType;
1293 DANGLING_END_ITEM_HELPER::sort_dangling_end_items( endPointsByType, endPointsByPos );
1294
1295 for( EDA_ITEM* item : aSelection )
1296 static_cast<SCH_ITEM*>( item )->UpdateDanglingState( endPointsByType, endPointsByPos );
1297}
1298
1299
1301 std::vector<DANGLING_END_ITEM>& aInternalPoints,
1302 GRID_HELPER_GRIDS& aSnapLayer )
1303{
1304 // Revert through the active operation's commit before collecting the new drag items.
1305 for( const HIDDEN_JUNCTION& hidden : m_hiddenJunctions )
1306 m_view->Hide( hidden.m_junction, false );
1307
1308 m_hiddenJunctions.clear();
1309
1310 m_selectionTool->RemoveItemsFromSel( &m_dragAdditions, QUIET_MODE );
1312 m_view->ClearPreview();
1313
1314 aCommit->Revert();
1315 m_changedDragLines.clear();
1316 m_lineConnectionCache.clear();
1317
1319 m_moveOffset = VECTOR2I( 0, 0 );
1320
1321 m_mode = aEvent.Parameter<MOVE_MODE>();
1322 m_moveInProgress = false;
1323 getViewControls()->SetAutoPan( false );
1324
1325 preprocessBreakOrSliceSelection( aCommit, aEvent );
1326 prepareMoveOperation( aSelection, aCommit, aInternalPoints, aSnapLayer );
1327
1328 if( m_mode == BREAK && m_breakPos )
1329 {
1331 aSelection.SetReferencePoint( *m_anchorPos );
1332 m_breakPos.reset();
1333 }
1334
1335 // Leave the actual cursor in place. The event loop reapplies the full displacement,
1336 // including orthogonal wire bends and attached label positioning.
1338 getViewControls()->SetAutoPan( true );
1339 m_moveInProgress = true;
1340}
1341
1342
1344 std::vector<DANGLING_END_ITEM>& aInternalPoints,
1345 GRID_HELPER_GRIDS& aSnapLayer )
1346{
1348
1349 //------------------------------------------------------------------------
1350 // Setup a drag or a move
1351 //
1352 m_dragAdditions.clear();
1353 m_specialCaseLabels.clear();
1354 m_specialCaseSheetPins.clear();
1355 m_sheetPinDragArc.clear();
1356 aInternalPoints.clear();
1358
1359 for( SCH_ITEM* it : m_frame->GetScreen()->Items() )
1360 {
1361 it->ClearFlags( SELECTED_BY_DRAG );
1362
1363 if( !it->IsSelected() )
1364 it->ClearFlags( STARTPOINT | ENDPOINT );
1365 }
1366
1367 setupItemsForDrag( aSelection, aCommit );
1368 setupItemsForMove( aSelection, aInternalPoints );
1369
1370 recordRedundantJunctions( aSelection );
1371
1372 // Generic setup
1373 aSnapLayer = grid.GetSelectionGrid( aSelection );
1374
1375 for( EDA_ITEM* item : aSelection )
1376 {
1377 SCH_ITEM* schItem = static_cast<SCH_ITEM*>( item );
1378
1379 if( schItem->IsNew() )
1380 {
1381 // Item was added to commit in a previous command
1382
1383 // While SCH_COMMIT::Push() will add any new items to the entered group, we need
1384 // to do it earlier so that the previews while moving are correct.
1385 if( SCH_GROUP* enteredGroup = m_selectionTool->GetEnteredGroup() )
1386 {
1387 if( schItem->IsGroupableType() && !schItem->GetParentGroup() )
1388 {
1389 aCommit->Modify( enteredGroup, m_frame->GetScreen(), RECURSE_MODE::NO_RECURSE );
1390 enteredGroup->AddItem( schItem );
1391 }
1392 }
1393 }
1394 else if( schItem->GetParent() && schItem->GetParent()->IsSelected() )
1395 {
1396 // Item will be (or has been) added to commit by parent
1397 }
1398 else
1399 {
1400 aCommit->Modify( schItem, m_frame->GetScreen(), RECURSE_MODE::RECURSE );
1401 }
1402
1403 schItem->SetFlags( IS_MOVING );
1404
1405 if( SCH_SHAPE* shape = dynamic_cast<SCH_SHAPE*>( schItem ) )
1406 {
1407 shape->SetHatchingDirty();
1408 shape->UpdateHatching();
1409 }
1410
1411 schItem->RunOnChildren(
1412 [&]( SCH_ITEM* aChild )
1413 {
1414 aChild->SetFlags( IS_MOVING );
1415 },
1417
1418 schItem->SetStoredPos( schItem->GetPosition() );
1419
1420 if( schItem->Type() == SCH_SHEET_PIN_T && schItem->GetParent() && !schItem->GetParent()->IsSelected() )
1421 {
1422 SCH_SHEET_PIN* pin = static_cast<SCH_SHEET_PIN*>( schItem );
1423 m_sheetPinDragArc[pin] = sheetBorderArc( pin->GetParent(), pin->GetSide(), pin->GetPosition() );
1424 }
1425 }
1426}
1427
1428
1430 std::vector<DANGLING_END_ITEM>& aInternalPoints, GRID_HELPER_GRIDS& aSnapLayer )
1431{
1434 SCH_ITEM* sch_item = static_cast<SCH_ITEM*>( aSelection.Front() );
1435 bool placingNewItems = sch_item && sch_item->IsNew();
1436
1437 prepareMoveOperation( aSelection, aCommit, aInternalPoints, aSnapLayer );
1438
1439 // Set up the starting position and move/drag offset
1440 m_cursor = controls->GetCursorPosition();
1441
1442 if( m_mode == BREAK && m_breakPos )
1443 {
1446 aSelection.SetReferencePoint( m_cursor );
1447 m_moveOffset = VECTOR2I( 0, 0 );
1448 m_breakPos.reset();
1449 }
1450
1451 if( placingNewItems )
1452 {
1453 m_anchorPos = aSelection.GetReferencePoint();
1454 }
1455
1456 if( m_anchorPos )
1457 {
1458 VECTOR2I delta = m_cursor - ( *m_anchorPos );
1459 bool isPasted = false;
1460
1461 // Drag items to the current cursor position
1462 for( EDA_ITEM* item : aSelection )
1463 {
1464 // Don't double move pins, fields, etc.
1465 if( item->GetParent() && item->GetParent()->IsSelected() )
1466 continue;
1467
1468 moveItem( item, delta );
1469 updateItem( item, false );
1470
1471 isPasted |= ( item->GetFlags() & IS_PASTED ) != 0;
1472 }
1473
1474 // The first time pasted items are moved we need to store the position of the cursor
1475 // so that rotate while moving works as expected (instead of around the original
1476 // anchor point)
1477 if( isPasted )
1478 aSelection.SetReferencePoint( m_cursor );
1479
1481 }
1482 // For some items, moving the cursor to anchor is not good (for instance large
1483 // hierarchical sheets or symbols can have the anchor outside the view)
1484 else if( aSelection.Size() == 1 && !sch_item->IsMovableFromAnchorPoint() )
1485 {
1488 }
1489 else
1490 {
1491 if( m_frame->GetMoveWarpsCursor() )
1492 {
1493 // User wants to warp the mouse
1494 m_cursor = grid.BestDragOrigin( m_cursor, aSnapLayer, aSelection );
1495 aSelection.SetReferencePoint( m_cursor );
1496 }
1497 else
1498 {
1499 // User does not want to warp the mouse
1501 }
1502 }
1503
1504 controls->SetCursorPosition( m_cursor, false );
1505
1506 controls->SetAutoPan( true );
1507 m_moveInProgress = true;
1508}
1509
1510
1512 bool aHasSheetPins, bool aIsGraphicsOnly, bool aCtrlDown )
1513{
1514 // Fields are children of their parent item and must not be dropped into a sheet
1515 for( EDA_ITEM* it : aSelection )
1516 {
1517 if( it->Type() == SCH_FIELD_T )
1518 return nullptr;
1519 }
1520
1521 // Determine potential target sheet
1522 SCH_SHEET* sheet = dynamic_cast<SCH_SHEET*>( m_frame->GetScreen()->GetItem( aCursorPos, 0, SCH_SHEET_T ) );
1523
1524 if( sheet && ( sheet->IsSelected() || sheet->HasFlag( IS_MOVING ) ) )
1525 sheet = nullptr; // Never target a selected sheet
1526
1527 if( !sheet )
1528 {
1529 // Build current selection bounding box in its (already moved) position
1530 BOX2I selBBox;
1531
1532 for( EDA_ITEM* it : aSelection )
1533 {
1534 if( SCH_ITEM* schIt = dynamic_cast<SCH_ITEM*>( it ) )
1535 selBBox.Merge( schIt->GetBoundingBox() );
1536 }
1537
1538 if( selBBox.GetWidth() > 0 && selBBox.GetHeight() > 0 )
1539 {
1540 VECTOR2I selCenter( selBBox.GetX() + selBBox.GetWidth() / 2,
1541 selBBox.GetY() + selBBox.GetHeight() / 2 );
1542
1543 // Find first non-selected sheet whose body fully contains the selection or at
1544 // least contains its center point
1545 for( SCH_ITEM* it : m_frame->GetScreen()->Items().OfType( SCH_SHEET_T ) )
1546 {
1547 SCH_SHEET* candidate = static_cast<SCH_SHEET*>( it );
1548
1549 if( candidate->IsSelected() || candidate->IsTopLevelSheet() || candidate->HasFlag( IS_MOVING ) )
1550 continue;
1551
1552 BOX2I body = candidate->GetBodyBoundingBox();
1553
1554 if( body.Contains( selBBox ) || body.Contains( selCenter ) )
1555 {
1556 sheet = candidate;
1557 break;
1558 }
1559 }
1560 }
1561 }
1562
1563 // Don't drop into a sheet if any connection point of the selection lands on a sheet pin.
1564 // This indicates the user is trying to connect to the pin, not drop into the sheet.
1565 if( sheet )
1566 {
1567 for( EDA_ITEM* it : aSelection )
1568 {
1569 SCH_ITEM* schItem = dynamic_cast<SCH_ITEM*>( it );
1570
1571 if( !schItem )
1572 continue;
1573
1574 for( const VECTOR2I& pt : schItem->GetConnectionPoints() )
1575 {
1576 if( sheet->GetPin( pt ) )
1577 {
1578 sheet = nullptr;
1579 break;
1580 }
1581 }
1582
1583 if( !sheet )
1584 break;
1585 }
1586 }
1587
1588 if( sheet && dropWouldRecurse( aSelection, sheet ) )
1589 sheet = nullptr;
1590
1591 bool dropAllowedBySelection = !aHasSheetPins;
1592 bool dropAllowedByModifiers = !aIsGraphicsOnly || aCtrlDown;
1593
1594 if( sheet && !( dropAllowedBySelection && dropAllowedByModifiers ) )
1595 sheet = nullptr;
1596
1597 return sheet;
1598}
1599
1600
1601bool SCH_MOVE_TOOL::dropWouldRecurse( const SCH_SELECTION& aSelection, const SCH_SHEET* aTargetSheet )
1602{
1603 SCH_SCREEN* destScreen = aTargetSheet->GetScreen();
1604
1605 if( !destScreen || destScreen->GetFileName().IsEmpty() )
1606 return false;
1607
1608 std::vector<SCH_SHEET*> movedSheets;
1609
1610 for( EDA_ITEM* item : aSelection )
1611 {
1612 if( item->Type() == SCH_SHEET_T )
1613 movedSheets.push_back( static_cast<SCH_SHEET*>( item ) );
1614 }
1615
1616 if( movedSheets.empty() )
1617 return false;
1618
1619 SCH_SHEET_LIST hierarchy = m_frame->Schematic().Hierarchy();
1620
1621 for( SCH_SHEET* movedSheet : movedSheets )
1622 {
1623 SCH_SHEET_LIST movedHierarchy( movedSheet );
1624
1625 if( hierarchy.TestForRecursion( movedHierarchy, destScreen->GetFileName() ) )
1626 return true;
1627 }
1628
1629 return false;
1630}
1631
1632
1634 SCH_COMMIT* aCommit, int& aXBendCount, int& aYBendCount,
1635 const EE_GRID_HELPER& aGrid )
1636{
1637 wxLogTrace( traceSchMove, "performItemMove: delta=(%d,%d), moveOffset=(%d,%d), selection size=%u",
1638 aDelta.x, aDelta.y, m_moveOffset.x, m_moveOffset.y, aSelection.GetSize() );
1639
1640 // We need to check if the movement will change the net offset direction on the X and Y
1641 // axes. This is because we remerge added bend lines in realtime, and we also account for
1642 // the direction of the move when adding bend lines. So, if the move direction changes,
1643 // we need to split it into a move that gets us back to zero, then the rest of the move.
1644 std::vector<VECTOR2I> splitMoves;
1645
1646 if( alg::signbit( m_moveOffset.x ) != alg::signbit( ( m_moveOffset + aDelta ).x ) )
1647 {
1648 splitMoves.emplace_back( VECTOR2I( -1 * m_moveOffset.x, 0 ) );
1649 splitMoves.emplace_back( VECTOR2I( aDelta.x + m_moveOffset.x, 0 ) );
1650 }
1651 else
1652 {
1653 splitMoves.emplace_back( VECTOR2I( aDelta.x, 0 ) );
1654 }
1655
1656 if( alg::signbit( m_moveOffset.y ) != alg::signbit( ( m_moveOffset + aDelta ).y ) )
1657 {
1658 splitMoves.emplace_back( VECTOR2I( 0, -1 * m_moveOffset.y ) );
1659 splitMoves.emplace_back( VECTOR2I( 0, aDelta.y + m_moveOffset.y ) );
1660 }
1661 else
1662 {
1663 splitMoves.emplace_back( VECTOR2I( 0, aDelta.y ) );
1664 }
1665
1666 m_moveOffset += aDelta;
1667
1668 // Split the move into X and Y moves so we can correctly drag orthogonal lines
1669 for( const VECTOR2I& splitDelta : splitMoves )
1670 {
1671 // Skip non-moves
1672 if( splitDelta == VECTOR2I( 0, 0 ) )
1673 continue;
1674
1675 for( EDA_ITEM* item : aSelection.GetItemsSortedByTypeAndXY( ( aDelta.x >= 0 ),
1676 ( aDelta.y >= 0 ) ) )
1677 {
1678 // Don't double move pins, fields, etc.
1679 if( item->GetParent() && item->GetParent()->IsSelected() )
1680 continue;
1681
1682 SCH_LINE* line = dynamic_cast<SCH_LINE*>( item );
1683 bool isLineModeConstrained = false;
1684
1685 if( EESCHEMA_SETTINGS* cfg = GetAppSettings<EESCHEMA_SETTINGS>( "eeschema" ) )
1686 isLineModeConstrained = cfg->m_Drawing.line_mode != LINE_MODE::LINE_MODE_FREE;
1687
1688 // Only partially selected drag lines in orthogonal line mode need special handling.
1689 // Skip newly-created connectivity wires added to maintain connectivity at junctions:
1690 // these are marked with both IS_NEW and SELECTED_BY_DRAG; they already have the
1691 // correct endpoint constraint and don't need orthogonal bending
1692 if( ( m_mode == DRAG ) && isLineModeConstrained && line
1693 && line->HasFlag( STARTPOINT ) != line->HasFlag( ENDPOINT )
1694 && !line->HasFlag( SELECTED_BY_DRAG | IS_NEW ) )
1695 {
1696 orthoLineDrag( aCommit, line, splitDelta, aXBendCount, aYBendCount, aGrid );
1697 }
1698
1699 // Move all other items normally, including the selected end of partially selected
1700 // lines
1701 moveItem( item, splitDelta );
1702 updateItem( item, false );
1703
1704 // Update any lines connected to sheet pins to the sheet pin's location (which may
1705 // not exactly follow the splitDelta as the pins are constrained along the sheet
1706 // edges)
1707 for( const auto& [pin, lineEnd] : m_specialCaseSheetPins )
1708 {
1709 if( lineEnd.second && lineEnd.first->HasFlag( STARTPOINT ) )
1710 lineEnd.first->SetStartPoint( pin->GetPosition() );
1711 else if( !lineEnd.second && lineEnd.first->HasFlag( ENDPOINT ) )
1712 lineEnd.first->SetEndPoint( pin->GetPosition() );
1713 }
1714 }
1715
1716 // Needed to keep labels attached to a line when dragging a sheet/wire combo with a label
1717 // on the line. The label moves by splitDelta for each part of the split move, but the
1718 // line endpoints may not follow splitDelta due to orthogonal drag or sheet pin constraints,
1719 // which can put the label off the line.
1720 for( auto& [label, info] : m_specialCaseLabels )
1721 {
1722 if( !label || !info.attachedLine )
1723 continue;
1724
1725 if( info.trackMovingEnd )
1726 {
1727 label->Move( splitDelta );
1728
1729 VECTOR2I start = info.attachedLine->GetStartPoint();
1730 VECTOR2I end = info.attachedLine->GetEndPoint();
1731
1732 if( info.attachedLine->GetLength() > 0
1733 && info.attachedLine->HitTest( info.originalLabelPos, 1 )
1734 && info.originalLabelPos != start
1735 && info.originalLabelPos != end )
1736 {
1737 info.trackMovingEnd = false;
1738 label->SetPosition( info.originalLabelPos );
1739 info.originalLineStart = start;
1740 info.originalLineEnd = end;
1741 }
1742
1743 updateItem( label, false );
1744 continue;
1745 }
1746
1747 VECTOR2I start = info.attachedLine->GetStartPoint();
1748 VECTOR2I end = info.attachedLine->GetEndPoint();
1749 VECTOR2I deltaStart = start - info.originalLineStart;
1750 VECTOR2I deltaEnd = end - info.originalLineEnd;
1751
1752 // TODO: this could be improved by positioning the label based on the new line geometry,
1753 // bends are involved.
1754 //
1755 // For now, special casing the equal delta case and using splitDelta should work in most
1756 // cases as the user would expect.
1757 if( deltaStart == deltaEnd )
1758 {
1759 label->SetPosition( info.originalLabelPos + deltaStart );
1760 }
1761 else
1762 {
1763 bool startDrags = info.attachedLine->HasFlag( STARTPOINT );
1764 VECTOR2I fixedEndDelta = startDrags ? deltaEnd : deltaStart;
1765
1766 label->SetPosition( info.originalLabelPos + fixedEndDelta );
1767
1768 // If the line shrank while dragging, keep the label on the line,
1769 // otherwise the label can drift off the end of the line, and change connectivity
1770 if( !info.attachedLine->HitTest( label->GetPosition(), 1 ) )
1771 {
1772 SEG seg( start, end );
1773 label->SetPosition( seg.NearestPoint( label->GetPosition() ) );
1774
1775 VECTOR2I movingEnd = startDrags ? start : end;
1776
1777 if( label->GetPosition() == movingEnd )
1778 info.trackMovingEnd = true;
1779 }
1780 }
1781
1782 updateItem( label, false );
1783 }
1784 }
1785
1786 spreadMovingSheetPinGroups( aSelection );
1787
1788 if( aSelection.HasReferencePoint() )
1789 aSelection.SetReferencePoint( aSelection.GetReferencePoint() + aDelta );
1790}
1791
1792
1794{
1795 // Slide pins dragged together by one distance along the border, so they keep their spacing
1796 // and wrap around corners instead of collapsing onto a shared point on a perpendicular edge.
1797 std::map<SCH_SHEET*, std::vector<SCH_SHEET_PIN*>> groups;
1798
1799 for( EDA_ITEM* item : aSelection )
1800 {
1801 if( item->Type() != SCH_SHEET_PIN_T )
1802 continue;
1803
1804 SCH_SHEET_PIN* pin = static_cast<SCH_SHEET_PIN*>( item );
1805
1806 if( SCH_SHEET* sheet = pin->GetParent(); sheet && !sheet->IsSelected() && m_sheetPinDragArc.count( pin ) )
1807 {
1808 groups[sheet].push_back( pin );
1809 }
1810 }
1811
1812 for( auto& [sheet, pins] : groups )
1813 {
1814 if( pins.size() < 2 )
1815 continue;
1816
1817 // Only slide as a group when the pins started on the same edge. A mix of edges would
1818 // move in opposite directions (the border runs one way), so leave those to the normal
1819 // per-pin constraint.
1820 auto startSide = [&]( SCH_SHEET_PIN* aPin )
1821 {
1822 VECTOR2I pos;
1823 SHEET_SIDE side;
1824 sheetBorderPos( sheet, m_sheetPinDragArc[aPin], pos, side );
1825 return side;
1826 };
1827
1828 SCH_SHEET_PIN* ref = pins.front();
1829 SHEET_SIDE refStartSide = startSide( ref );
1830 bool sameEdge = true;
1831
1832 for( SCH_SHEET_PIN* pin : pins )
1833 sameEdge &= ( startSide( pin ) == refStartSide );
1834
1835 if( !sameEdge )
1836 continue;
1837
1838 // The reference pin is already on its edge, its border travel drives the group slide.
1839 long long refArc = sheetBorderArc( sheet, ref->GetSide(), ref->GetPosition() );
1840 long long slide = refArc - m_sheetPinDragArc[ref];
1841
1842 for( SCH_SHEET_PIN* pin : pins )
1843 {
1844 VECTOR2I pos;
1845 SHEET_SIDE side;
1846 sheetBorderPos( sheet, m_sheetPinDragArc[pin] + slide, pos, side );
1847
1848 pin->SetSide( side );
1849
1850 if( side == SHEET_SIDE::LEFT || side == SHEET_SIDE::RIGHT )
1851 pin->SetTextY( pos.y );
1852 else
1853 pin->SetTextX( pos.x );
1854
1855 updateItem( pin, false );
1856 }
1857 }
1858
1859 // Pull attached lines back to the moved pins.
1860 for( const auto& [pin, lineEnd] : m_specialCaseSheetPins )
1861 {
1862 if( lineEnd.second && lineEnd.first->HasFlag( STARTPOINT ) )
1863 lineEnd.first->SetStartPoint( pin->GetPosition() );
1864 else if( !lineEnd.second && lineEnd.first->HasFlag( ENDPOINT ) )
1865 lineEnd.first->SetEndPoint( pin->GetPosition() );
1866 }
1867}
1868
1869
1871 const SCH_SELECTION& aSelection )
1872{
1873 wxLogTrace( traceSchMove, "handleMoveToolActions: received event, action=%s",
1874 aEvent->Format().c_str() );
1875
1876 if( aEvent->IsAction( &ACTIONS::doDelete ) )
1877 {
1878 wxLogTrace( traceSchMove, "handleMoveToolActions: doDelete, exiting move" );
1879 const_cast<TOOL_EVENT*>( aEvent )->SetPassEvent();
1880 return false; // Exit on delete; there will no longer be anything to drag
1881 }
1882 else if( aEvent->IsAction( &ACTIONS::duplicate )
1884 || aEvent->IsAction( &ACTIONS::redo ) )
1885 {
1886 wxBell();
1887 }
1888 else if( aEvent->IsAction( &SCH_ACTIONS::rotateCW ) )
1889 {
1890 wxLogTrace( traceSchMove, "handleMoveToolActions: rotateCW event received, selection size=%u",
1891 aSelection.GetSize() );
1892 m_toolMgr->RunSynchronousAction( SCH_ACTIONS::rotateCW, aCommit );
1893 wxLogTrace( traceSchMove, "handleMoveToolActions: rotateCW RunSynchronousAction completed" );
1894 updateStoredPositions( aSelection );
1895 wxLogTrace( traceSchMove, "handleMoveToolActions: rotateCW updateStoredPositions completed" );
1896 // Note: SCH_EDIT_TOOL::Rotate already posts refreshPreview when moving
1897 }
1898 else if( aEvent->IsAction( &SCH_ACTIONS::rotateCCW ) )
1899 {
1900 wxLogTrace( traceSchMove, "handleMoveToolActions: rotateCCW event received, selection size=%u",
1901 aSelection.GetSize() );
1902 m_toolMgr->RunSynchronousAction( SCH_ACTIONS::rotateCCW, aCommit );
1903 wxLogTrace( traceSchMove, "handleMoveToolActions: rotateCCW RunSynchronousAction completed" );
1904 updateStoredPositions( aSelection );
1905 wxLogTrace( traceSchMove, "handleMoveToolActions: rotateCCW updateStoredPositions completed" );
1906 // Note: SCH_EDIT_TOOL::Rotate already posts refreshPreview when moving
1907 }
1908 else if( aEvent->IsAction( &ACTIONS::increment ) )
1909 {
1910 if( aEvent->HasParameter() )
1911 m_toolMgr->RunSynchronousAction( ACTIONS::increment, aCommit, aEvent->Parameter<ACTIONS::INCREMENT>() );
1912 else
1913 m_toolMgr->RunSynchronousAction( ACTIONS::increment, aCommit, ACTIONS::INCREMENT{ 1, 0 } );
1914
1915 updateStoredPositions( aSelection );
1916 m_toolMgr->PostAction( ACTIONS::refreshPreview );
1917 }
1918 else if( aEvent->IsAction( &SCH_ACTIONS::toDLabel ) )
1919 {
1920 m_toolMgr->RunSynchronousAction( SCH_ACTIONS::toDLabel, aCommit );
1921 updateStoredPositions( aSelection );
1922 m_toolMgr->PostAction( ACTIONS::refreshPreview );
1923 }
1924 else if( aEvent->IsAction( &SCH_ACTIONS::toGLabel ) )
1925 {
1926 m_toolMgr->RunSynchronousAction( SCH_ACTIONS::toGLabel, aCommit );
1927 updateStoredPositions( aSelection );
1928 m_toolMgr->PostAction( ACTIONS::refreshPreview );
1929 }
1930 else if( aEvent->IsAction( &SCH_ACTIONS::toHLabel ) )
1931 {
1932 m_toolMgr->RunSynchronousAction( SCH_ACTIONS::toHLabel, aCommit );
1933 updateStoredPositions( aSelection );
1934 m_toolMgr->PostAction( ACTIONS::refreshPreview );
1935 }
1936 else if( aEvent->IsAction( &SCH_ACTIONS::toLabel ) )
1937 {
1938 m_toolMgr->RunSynchronousAction( SCH_ACTIONS::toLabel, aCommit );
1939 updateStoredPositions( aSelection );
1940 m_toolMgr->PostAction( ACTIONS::refreshPreview );
1941 }
1942 else if( aEvent->IsAction( &SCH_ACTIONS::toText ) )
1943 {
1944 m_toolMgr->RunSynchronousAction( SCH_ACTIONS::toText, aCommit );
1945 updateStoredPositions( aSelection );
1946 m_toolMgr->PostAction( ACTIONS::refreshPreview );
1947 }
1948 else if( aEvent->IsAction( &SCH_ACTIONS::toTextBox ) )
1949 {
1950 m_toolMgr->RunSynchronousAction( SCH_ACTIONS::toTextBox, aCommit );
1951 updateStoredPositions( aSelection );
1952 m_toolMgr->PostAction( ACTIONS::refreshPreview );
1953 }
1954 else if( aEvent->Action() == TA_CHOICE_MENU_CHOICE )
1955 {
1956 if( *aEvent->GetCommandId() >= ID_POPUP_SCH_SELECT_UNIT
1958 {
1959 SCH_SYMBOL* symbol = dynamic_cast<SCH_SYMBOL*>( m_selectionTool->GetSelection().Front() );
1960 int unit = *aEvent->GetCommandId() - ID_POPUP_SCH_SELECT_UNIT;
1961
1962 if( symbol )
1963 {
1964 m_frame->SelectUnit( symbol, unit, aCommit );
1965 m_toolMgr->PostAction( ACTIONS::refreshPreview );
1966 }
1967 }
1968 else if( *aEvent->GetCommandId() >= ID_POPUP_SCH_SELECT_BODY_STYLE
1970 {
1971 SCH_SYMBOL* symbol = dynamic_cast<SCH_SYMBOL*>( m_selectionTool->GetSelection().Front() );
1972 int bodyStyle = ( *aEvent->GetCommandId() - ID_POPUP_SCH_SELECT_BODY_STYLE ) + 1;
1973
1974 if( symbol && symbol->GetBodyStyle() != bodyStyle )
1975 {
1976 m_frame->SelectBodyStyle( symbol, bodyStyle, aCommit );
1977 m_toolMgr->PostAction( ACTIONS::refreshPreview );
1978 }
1979 }
1980 }
1981 else if( aEvent->IsAction( &SCH_ACTIONS::highlightNet )
1982 || aEvent->IsAction( &SCH_ACTIONS::selectOnPCB ) )
1983 {
1984 // These don't make any sense during a move. Eat them.
1985 }
1986 else
1987 {
1988 return true; // Continue processing
1989 }
1990
1991 return true; // Continue processing
1992}
1993
1994
1996{
1997 wxLogTrace( traceSchMove, "updateStoredPositions: start, selection size=%u",
1998 aSelection.GetSize() );
1999
2000 // After transformations like rotation during a move, we need to update the stored
2001 // positions that moveItem() uses, particularly for sheet pins which rely on them
2002 // for constraint calculations.
2003 int itemCount = 0;
2004
2005 for( EDA_ITEM* item : aSelection )
2006 {
2007 SCH_ITEM* schItem = dynamic_cast<SCH_ITEM*>( item );
2008
2009 if( !schItem )
2010 continue;
2011
2012 VECTOR2I oldPos = schItem->GetStoredPos();
2013 VECTOR2I newPos = schItem->GetPosition();
2014 schItem->SetStoredPos( newPos );
2015
2016 // Re-baseline the pin's border distance after a transform (e.g. rotation).
2017 if( schItem->Type() == SCH_SHEET_PIN_T )
2018 {
2019 SCH_SHEET_PIN* pin = static_cast<SCH_SHEET_PIN*>( schItem );
2020
2021 if( m_sheetPinDragArc.count( pin ) )
2022 m_sheetPinDragArc[pin] = sheetBorderArc( pin->GetParent(), pin->GetSide(), pin->GetPosition() );
2023 }
2024
2025 wxLogTrace( traceSchMove, " item[%d] type=%d: stored pos updated (%d,%d) -> (%d,%d)",
2026 itemCount++, (int) schItem->Type(), oldPos.x, oldPos.y, newPos.x, newPos.y );
2027
2028 // Also update stored positions for sheet pins
2029 if( schItem->Type() == SCH_SHEET_T )
2030 {
2031 SCH_SHEET* sheet = static_cast<SCH_SHEET*>( schItem );
2032 for( SCH_SHEET_PIN* pin : sheet->GetPins() )
2033 {
2034 VECTOR2I pinOldPos = pin->GetStoredPos();
2035 VECTOR2I pinNewPos = pin->GetPosition();
2036 pin->SetStoredPos( pinNewPos );
2037 wxLogTrace( traceSchMove, " sheet pin: stored pos updated (%d,%d) -> (%d,%d)",
2038 pinOldPos.x, pinOldPos.y, pinNewPos.x, pinNewPos.y );
2039 }
2040 }
2041 }
2042
2043 wxLogTrace( traceSchMove, "updateStoredPositions: complete, updated %d items", itemCount );
2044}
2045
2046
2048{
2049 m_hiddenJunctions.clear();
2050
2051 for( EDA_ITEM* item : aSelection )
2052 item->SetFlags( STRUCT_DELETED );
2053
2054 for( EDA_ITEM* edaItem : aSelection )
2055 {
2056 if( edaItem->Type() != SCH_LINE_T )
2057 continue;
2058
2059 SCH_LINE* line = static_cast<SCH_LINE*>( edaItem );
2060
2061 for( const VECTOR2I& pt : line->GetConnectionPoints() )
2062 {
2063 SCH_JUNCTION* jct = static_cast<SCH_JUNCTION*>( m_frame->GetScreen()->GetItem( pt, 0, SCH_JUNCTION_T ) );
2064
2065 if( jct && !jct->IsSelected()
2066 && std::none_of( m_hiddenJunctions.begin(), m_hiddenJunctions.end(),
2067 [jct]( const HIDDEN_JUNCTION& aHidden )
2068 {
2069 return aHidden.m_junction == jct;
2070 } ) )
2071 {
2073 JUNCTION_HELPERS::AnalyzePoint( m_frame->GetScreen()->Items(), pt, false );
2074
2075 if( !info.isJunction )
2076 {
2077 m_hiddenJunctions.push_back( { jct, line->m_Uuid, pt == line->GetStartPoint() } );
2078 m_view->Hide( jct, true );
2079 }
2080 }
2081 }
2082 }
2083
2084 for( EDA_ITEM* item : aSelection )
2085 item->ClearFlags( STRUCT_DELETED );
2086}
2087
2088
2090{
2091 SCH_SCREEN* screen = m_frame->GetScreen();
2092
2093 for( const HIDDEN_JUNCTION& hidden : m_hiddenJunctions )
2094 {
2095 m_view->Hide( hidden.m_junction, false );
2096
2097 SCH_LINE* line = dynamic_cast<SCH_LINE*>( m_frame->Schematic().ResolveItem( hidden.m_lineId, nullptr, true ) );
2098
2099 if( !line )
2100 continue;
2101
2102 VECTOR2I newPos = hidden.m_atLineStart ? line->GetStartPoint() : line->GetEndPoint();
2103
2104 if( newPos != hidden.m_junction->GetPosition()
2105 && !screen->IsExplicitJunction( hidden.m_junction->GetPosition() )
2106 && screen->IsExplicitJunctionNeeded( newPos ) )
2107 {
2108 aCommit->Modify( hidden.m_junction, screen );
2109 hidden.m_junction->SetPosition( newPos );
2110 m_frame->UpdateItem( hidden.m_junction, false, true );
2111 }
2112 }
2113}
2114
2115
2116void SCH_MOVE_TOOL::finalizeMoveOperation( SCH_SELECTION& aSelection, SCH_COMMIT* aCommit, bool aUnselect,
2117 const std::vector<DANGLING_END_ITEM>& aInternalPoints )
2118{
2120 const bool isSlice = ( m_mode == SLICE );
2121 const bool isDragLike = ( m_mode == DRAG || m_mode == BREAK );
2122
2123 // Save whatever new bend lines and changed lines survived the drag
2124 for( SCH_LINE* newLine : m_newDragLines )
2125 {
2126 newLine->ClearEditFlags();
2127 aCommit->Added( newLine, m_frame->GetScreen() );
2128 }
2129
2130 // These lines have been changed, but aren't selected. We need to manually clear these
2131 // edit flags or they'll stick around.
2132 for( SCH_LINE* oldLine : m_changedDragLines )
2133 oldLine->ClearEditFlags();
2134
2135 controls->ForceCursorPosition( false );
2136 controls->ShowCursor( false );
2137 controls->SetAutoPan( false );
2138
2139 m_moveOffset = { 0, 0 };
2140 m_anchorPos.reset();
2141
2142 // One last update after exiting loop (for slower stuff, such as updating SCREEN's RTree)
2143 for( EDA_ITEM* item : aSelection )
2144 {
2145 updateItem( item, true );
2146
2147 if( SCH_ITEM* sch_item = dynamic_cast<SCH_ITEM*>( item ) )
2148 sch_item->SetConnectivityDirty( true );
2149 }
2150
2151 if( aSelection.GetSize() == 1 && aSelection.Front()->IsNew() )
2152 m_frame->SaveCopyForRepeatItem( static_cast<SCH_ITEM*>( aSelection.Front() ) );
2153
2154 m_selectionTool->RemoveItemsFromSel( &m_dragAdditions, QUIET_MODE );
2155
2157
2158 // If we move items away from a junction, we _may_ want to add a junction there
2159 // to denote the state
2160 for( const DANGLING_END_ITEM& it : aInternalPoints )
2161 {
2162 if( m_frame->GetScreen()->IsExplicitJunctionNeeded( it.GetPosition() ) )
2163 lwbTool->AddJunction( aCommit, m_frame->GetScreen(), it.GetPosition() );
2164 }
2165
2166 // Create a selection of original selection, drag selected/changed items, and new bend
2167 // lines for later before we clear them in the aCommit. We'll need these to check for new
2168 // junctions needed, etc.
2169 SCH_SELECTION selectionCopy( aSelection );
2170
2171 for( SCH_LINE* line : m_newDragLines )
2172 selectionCopy.Add( line );
2173
2174 for( SCH_LINE* line : m_changedDragLines )
2175 selectionCopy.Add( line );
2176
2177 lwbTool->TrimOverLappingWires( aCommit, &selectionCopy );
2178
2179 migrateHiddenJunctions( aCommit );
2180
2181 lwbTool->AddJunctionsIfNeeded( aCommit, &selectionCopy );
2182
2183 // This needs to run prior to `RecalculateConnections` because we need to identify the
2184 // lines that are newly dangling
2185 if( isDragLike && !isSlice )
2186 trimDanglingLines( aCommit );
2187
2188 // Auto-rotate any moved labels
2189 for( EDA_ITEM* item : aSelection )
2190 m_frame->AutoRotateItem( m_frame->GetScreen(), static_cast<SCH_ITEM*>( item ) );
2191
2192 // Clear SELECTED_BY_DRAG and other temp flags before CleanUp so that cleanup can properly
2193 // process all items, including removing zero-length wires and unwanted stubs
2194 for( EDA_ITEM* item : m_frame->GetScreen()->Items() )
2195 item->ClearTempFlags();
2196
2197 for( EDA_ITEM* item : selectionCopy )
2198 item->ClearTempFlags();
2199
2200 m_frame->Schematic().CleanUp( aCommit );
2201
2202 // Mirror the IS_MOVING flag propagation done at the start of the move so that child items
2203 // (e.g. label fields, symbol pins/fields) don't keep their edit flags after the move ends.
2204 auto clearChildEditFlags =
2205 []( SCH_ITEM* aItem )
2206 {
2207 aItem->RunOnChildren(
2208 []( SCH_ITEM* aChild )
2209 {
2210 aChild->ClearEditFlags();
2211 },
2213 };
2214
2215 for( EDA_ITEM* item : m_frame->GetScreen()->Items() )
2216 {
2217 item->ClearEditFlags();
2218
2219 if( SCH_ITEM* schItem = dynamic_cast<SCH_ITEM*>( item ) )
2220 clearChildEditFlags( schItem );
2221 }
2222
2223 // Ensure any selected item not in screen main list (for instance symbol fields) has its
2224 // edit flags cleared
2225 for( EDA_ITEM* item : selectionCopy )
2226 {
2227 item->ClearEditFlags();
2228
2229 if( SCH_ITEM* schItem = dynamic_cast<SCH_ITEM*>( item ) )
2230 clearChildEditFlags( schItem );
2231 }
2232
2233 m_newDragLines.clear();
2234 m_changedDragLines.clear();
2235
2236 if( aUnselect )
2237 m_toolMgr->RunAction( ACTIONS::selectionClear );
2238 else
2239 m_selectionTool->RebuildSelection(); // Schematic cleanup might have merged lines, etc.
2240}
2241
2242
2244 SCH_COMMIT* aCommit )
2245{
2246 SCH_SCREEN* destScreen = aTargetSheet->GetScreen();
2247 SCH_SCREEN* srcScreen = m_frame->GetScreen();
2248
2249 BOX2I bbox;
2250
2251 for( EDA_ITEM* item : aSelection )
2252 bbox.Merge( static_cast<SCH_ITEM*>( item )->GetBoundingBox() );
2253
2254 VECTOR2I offset = VECTOR2I( 0, 0 ) - bbox.GetPosition();
2255 int step = schIUScale.MilsToIU( 50 );
2256 bool overlap = false;
2257
2258 do
2259 {
2260 BOX2I moved = bbox;
2261 moved.Move( offset );
2262 overlap = false;
2263
2264 for( SCH_ITEM* existing : destScreen->Items() )
2265 {
2266 if( moved.Intersects( existing->GetBoundingBox() ) )
2267 {
2268 overlap = true;
2269 break;
2270 }
2271 }
2272
2273 if( overlap )
2274 offset += VECTOR2I( step, step );
2275 } while( overlap );
2276
2277 for( EDA_ITEM* item : aSelection )
2278 {
2279 SCH_ITEM* schItem = static_cast<SCH_ITEM*>( item );
2280
2281 // Remove from current screen and view manually
2282 m_frame->RemoveFromScreen( schItem, srcScreen );
2283
2284 // Move the item
2285 schItem->Move( offset );
2286
2287 // Add to destination screen manually (won't add to view since it's not current)
2288 destScreen->Append( schItem );
2289
2290 // Record in commit with CHT_DONE flag to bypass automatic screen/view operations
2291 aCommit->Stage( schItem, CHT_REMOVE | CHT_DONE, srcScreen );
2292 aCommit->Stage( schItem, CHT_ADD | CHT_DONE, destScreen );
2293 }
2294}
2295
2296
2298{
2299 // Need a local cleanup first to ensure we remove unneeded junctions
2300 m_frame->Schematic().CleanUp( aCommit, m_frame->GetScreen() );
2301
2302 std::set<SCH_ITEM*> danglers;
2303
2304 std::function<void( SCH_ITEM* )> changeHandler =
2305 [&]( SCH_ITEM* aChangedItem ) -> void
2306 {
2307 m_toolMgr->GetView()->Update( aChangedItem, KIGFX::REPAINT );
2308
2309 if( aChangedItem->IsSelected() )
2310 return;
2311
2312 SCH_LINE* line = dynamic_cast<SCH_LINE*>( aChangedItem );
2313
2314 if( !line )
2315 return;
2316
2317 // Split segments that are dangling get trimmed back since they extend
2318 // past the break point.
2319 if( line->HasFlag( IS_BROKEN ) && line->IsDangling() )
2320 {
2321 danglers.insert( aChangedItem );
2322 }
2323 // Drag wires that are completely disconnected (both ends dangling) are
2324 // stubs that should be removed. Wires with only one connected end are
2325 // still providing connectivity and must be preserved.
2326 else if( line->HasFlag( IS_NEW ) && !line->HasFlag( IS_BROKEN )
2327 && line->IsStartDangling() && line->IsEndDangling() )
2328 {
2329 danglers.insert( aChangedItem );
2330 }
2331 };
2332
2333 m_frame->GetScreen()->TestDanglingEnds( nullptr, &changeHandler );
2334
2335 for( SCH_ITEM* line : danglers )
2336 {
2337 line->SetFlags( STRUCT_DELETED );
2338 aCommit->Removed( line, m_frame->GetScreen() );
2339 updateItem( line, false ); // Update any cached visuals before commit processes
2340 m_frame->RemoveFromScreen( line, m_frame->GetScreen() );
2341 }
2342}
2343
2344
2345void SCH_MOVE_TOOL::getConnectedItems( SCH_ITEM* aOriginalItem, const VECTOR2I& aPoint, EDA_ITEMS& aList )
2346{
2347 EE_RTREE& items = m_frame->GetScreen()->Items();
2348 EE_RTREE::EE_TYPE itemsOverlapping = items.Overlapping( aOriginalItem->GetBoundingBox() );
2349 SCH_ITEM* foundJunction = nullptr;
2350 SCH_ITEM* foundSymbol = nullptr;
2351
2352 // If you're connected to a junction, you're only connected to the junction.
2353 //
2354 // But, if you're connected to a junction on a pin, you're only connected to the pin. This
2355 // is because junctions and pins have different logic for how bend lines are generated and
2356 // we need to prioritize the pin version in some cases.
2357 for( SCH_ITEM* item : itemsOverlapping )
2358 {
2359 if( item != aOriginalItem && item->IsConnected( aPoint ) )
2360 {
2361 if( item->Type() == SCH_JUNCTION_T )
2362 foundJunction = item;
2363 else if( item->Type() == SCH_SYMBOL_T )
2364 foundSymbol = item;
2365 }
2366 }
2367
2368 if( foundSymbol && foundJunction )
2369 {
2370 aList.push_back( foundSymbol );
2371 return;
2372 }
2373
2374 if( foundJunction )
2375 {
2376 aList.push_back( foundJunction );
2377 return;
2378 }
2379
2380
2381 for( SCH_ITEM* test : itemsOverlapping )
2382 {
2383 if( test == aOriginalItem || !test->CanConnect( aOriginalItem ) )
2384 continue;
2385
2386 switch( test->Type() )
2387 {
2388 case SCH_LINE_T:
2389 {
2390 SCH_LINE* line = static_cast<SCH_LINE*>( test );
2391
2392 // When getting lines for the connection cache, it's important that we only add
2393 // items at the unselected end, since that is the only end that is handled specially.
2394 // Fully selected lines, and the selected end of a partially selected line, are moved
2395 // around normally and don't care about their connections.
2396 if( ( line->HasFlag( STARTPOINT ) && aPoint == line->GetStartPoint() )
2397 || ( line->HasFlag( ENDPOINT ) && aPoint == line->GetEndPoint() ) )
2398 {
2399 continue;
2400 }
2401
2402 if( test->IsConnected( aPoint ) )
2403 aList.push_back( test );
2404
2405 // Labels can connect to a wire (or bus) anywhere along the length
2406 if( SCH_LABEL_BASE* label = dynamic_cast<SCH_LABEL_BASE*>( aOriginalItem ) )
2407 {
2408 if( static_cast<SCH_LINE*>( test )->HitTest( label->GetPosition(), 1 ) )
2409 aList.push_back( test );
2410 }
2411
2412 break;
2413 }
2414
2415 case SCH_SHEET_T:
2416 if( aOriginalItem->Type() == SCH_LINE_T )
2417 {
2418 SCH_LINE* line = static_cast<SCH_LINE*>( aOriginalItem );
2419
2420 for( SCH_SHEET_PIN* pin : static_cast<SCH_SHEET*>( test )->GetPins() )
2421 {
2422 if( pin->IsConnected( aPoint ) )
2423 {
2424 if( pin->IsSelected() )
2425 m_specialCaseSheetPins[pin] = { line, line->GetStartPoint() == aPoint };
2426
2427 aList.push_back( pin );
2428 }
2429 }
2430 }
2431
2432 break;
2433
2434 case SCH_SYMBOL_T:
2435 case SCH_JUNCTION_T:
2436 case SCH_NO_CONNECT_T:
2437 if( test->IsConnected( aPoint ) )
2438 aList.push_back( test );
2439
2440 break;
2441
2442 case SCH_LABEL_T:
2443 case SCH_GLOBAL_LABEL_T:
2444 case SCH_HIER_LABEL_T:
2446 // Labels can connect to a wire (or bus) anywhere along the length
2447 if( aOriginalItem->Type() == SCH_LINE_T && test->CanConnect( aOriginalItem ) )
2448 {
2449 SCH_LABEL_BASE* label = static_cast<SCH_LABEL_BASE*>( test );
2450 SCH_LINE* line = static_cast<SCH_LINE*>( aOriginalItem );
2451
2452 if( line->HitTest( label->GetPosition(), 1 ) )
2453 aList.push_back( label );
2454 }
2455
2456 break;
2457
2460 if( aOriginalItem->Type() == SCH_LINE_T && test->CanConnect( aOriginalItem ) )
2461 {
2462 SCH_LINE* line = static_cast<SCH_LINE*>( aOriginalItem );
2463
2464 for( const VECTOR2I& point : test->GetConnectionPoints() )
2465 {
2466 if( line->HitTest( point, 1 ) )
2467 {
2468 aList.push_back( test );
2469 break;
2470 }
2471 }
2472 }
2473
2474 break;
2475
2476 default:
2477 break;
2478 }
2479 }
2480}
2481
2482
2483void SCH_MOVE_TOOL::getConnectedDragItems( SCH_COMMIT* aCommit, SCH_ITEM* aSelectedItem, const VECTOR2I& aPoint,
2484 EDA_ITEMS& aList )
2485{
2486 EE_RTREE& items = m_frame->GetScreen()->Items();
2487 std::set<SCH_ITEM*> connectableCandidates;
2488 std::vector<SCH_ITEM*> itemsConnectable;
2489 bool ptHasUnselectedJunction = false;
2490
2491 for( SCH_ITEM* item : items.Overlapping( aSelectedItem->GetBoundingBox() ) )
2492 connectableCandidates.insert( item );
2493
2494 // Labels can connect at their anchor even if the label bbox doesn't overlap the target, e.g.
2495 // sheet pins can do this sometimes with just net labels and no wires.
2496 if( dynamic_cast<SCH_LABEL_BASE*>( aSelectedItem ) )
2497 {
2498 for( SCH_ITEM* item : items.Overlapping( aPoint, 1 ) )
2499 connectableCandidates.insert( item );
2500 }
2501
2502 auto makeNewWire =
2503 [this]( SCH_COMMIT* commit, SCH_ITEM* fixed, SCH_ITEM* selected, const VECTOR2I& start,
2504 const VECTOR2I& end )
2505 {
2506 SCH_LINE* newWire;
2507 bool isBusLabel = false;
2508
2509 if( SCH_LABEL_BASE* label = dynamic_cast<SCH_LABEL_BASE*>( fixed ) )
2510 isBusLabel |= SCH_CONNECTION::IsBusLabel( label->GetText() );
2511
2512 if( SCH_LABEL_BASE* label = dynamic_cast<SCH_LABEL_BASE*>( selected ) )
2513 isBusLabel |= SCH_CONNECTION::IsBusLabel( label->GetText() );
2514
2515 // Add a new newWire between the fixed item and the selected item so the selected
2516 // item can be dragged.
2517 if( fixed->GetLayer() == LAYER_BUS_JUNCTION || fixed->GetLayer() == LAYER_BUS
2518 || selected->GetLayer() == LAYER_BUS || isBusLabel )
2519 {
2520 newWire = new SCH_LINE( start, LAYER_BUS );
2521 }
2522 else
2523 {
2524 newWire = new SCH_LINE( start, LAYER_WIRE );
2525 }
2526
2527 newWire->SetFlags( IS_NEW );
2528 newWire->SetConnectivityDirty( true );
2529
2530 SCH_LINE* selectedLine = dynamic_cast<SCH_LINE*>( selected );
2531 SCH_LINE* fixedLine = dynamic_cast<SCH_LINE*>( fixed );
2532
2533 if( selectedLine )
2534 {
2535 newWire->SetLastResolvedState( selected );
2536 cloneWireConnection( newWire, selectedLine, m_frame );
2537 }
2538 else if( fixedLine )
2539 {
2540 newWire->SetLastResolvedState( fixed );
2541 cloneWireConnection( newWire, fixedLine, m_frame );
2542 }
2543
2544 newWire->SetEndPoint( end );
2545 m_frame->AddToScreen( newWire, m_frame->GetScreen() );
2546 commit->Added( newWire, m_frame->GetScreen() );
2547
2548 return newWire;
2549 };
2550
2551 auto makeNewJunction =
2552 [this]( SCH_COMMIT* commit, SCH_LINE* line, const VECTOR2I& pt )
2553 {
2554 SCH_JUNCTION* junction = new SCH_JUNCTION( pt );
2555 junction->SetFlags( IS_NEW );
2556 junction->SetConnectivityDirty( true );
2557 junction->SetLastResolvedState( line );
2558
2559 if( line->IsBus() )
2560 junction->SetLayer( LAYER_BUS_JUNCTION );
2561
2562 m_frame->AddToScreen( junction, m_frame->GetScreen() );
2563 commit->Added( junction, m_frame->GetScreen() );
2564
2565 return junction;
2566 };
2567
2568 for( SCH_ITEM* item : connectableCandidates )
2569 {
2570 if( item->Type() == SCH_SHEET_T )
2571 {
2572 SCH_SHEET* sheet = static_cast<SCH_SHEET*>( item );
2573
2574 // A sheet inside a selected group moves with the group, so its pins should not be
2575 // treated as fixed connection anchors.
2576 if( sheet->HasSelectedAncestorGroup() )
2577 continue;
2578
2579 for( SCH_SHEET_PIN* pin : sheet->GetPins() )
2580 {
2581 if( !pin->IsSelected()
2582 && pin->GetPosition() == aPoint
2583 && pin->CanConnect( aSelectedItem ) )
2584 {
2585 itemsConnectable.push_back( pin );
2586 }
2587 }
2588
2589 continue;
2590 }
2591
2592 // Skip ourselves, skip already selected items (but not lines, they need both ends tested)
2593 // and skip unconnectable items. Items inside a selected group are also moving with the
2594 // selection even though they do not carry the SELECTED flag themselves; treating them as
2595 // fixed anchors causes spurious stub wires to be created at the group boundary.
2596 if( item == aSelectedItem
2597 || ( item->Type() != SCH_LINE_T && ( item->IsSelected() || item->HasSelectedAncestorGroup() ) )
2598 || !item->CanConnect( aSelectedItem ) )
2599 {
2600 continue;
2601 }
2602
2603 itemsConnectable.push_back( item );
2604 }
2605
2606 for( SCH_ITEM* item : itemsConnectable )
2607 {
2608 if( item->Type() == SCH_JUNCTION_T && item->IsConnected( aPoint ) && !item->IsSelected() )
2609 {
2610 ptHasUnselectedJunction = true;
2611 break;
2612 }
2613 }
2614
2615 SCH_LINE* newWire = nullptr;
2616
2617 for( SCH_ITEM* test : itemsConnectable )
2618 {
2619 KICAD_T testType = test->Type();
2620
2621 switch( testType )
2622 {
2623 case SCH_LINE_T:
2624 {
2625 // Select the connected end of wires/bus connections that don't have an unselected
2626 // junction isolating them from the drag
2627 if( ptHasUnselectedJunction )
2628 break;
2629
2630 SCH_LINE* line = static_cast<SCH_LINE*>( test );
2631
2632 // A line that is itself a member of a selected group is already moving with that
2633 // group; do not add it as a drag attachment or it will move twice.
2634 bool lineInSelectedGroup = line->HasSelectedAncestorGroup();
2635
2636 if( line->GetStartPoint() == aPoint )
2637 {
2638 // It's possible to manually select one end of a line and get a drag
2639 // connected other end, so we set the flag and then early exit the loop
2640 // later if the other drag items like labels attached to the line have
2641 // already been grabbed during the partial selection process.
2642 if( !lineInSelectedGroup )
2643 line->SetFlags( STARTPOINT );
2644
2645 if( line->HasFlag( SELECTED ) || line->HasFlag( SELECTED_BY_DRAG )
2646 || lineInSelectedGroup )
2647 {
2648 continue;
2649 }
2650 else
2651 {
2652 line->SetFlags( SELECTED_BY_DRAG );
2653 aList.push_back( line );
2654 }
2655 }
2656 else if( line->GetEndPoint() == aPoint )
2657 {
2658 if( !lineInSelectedGroup )
2659 line->SetFlags( ENDPOINT );
2660
2661 if( line->HasFlag( SELECTED ) || line->HasFlag( SELECTED_BY_DRAG )
2662 || lineInSelectedGroup )
2663 {
2664 continue;
2665 }
2666 else
2667 {
2668 line->SetFlags( SELECTED_BY_DRAG );
2669 aList.push_back( line );
2670 }
2671 }
2672 else
2673 {
2674 switch( aSelectedItem->Type() )
2675 {
2676 // These items can connect anywhere along a line
2679 case SCH_LABEL_T:
2680 case SCH_HIER_LABEL_T:
2681 case SCH_GLOBAL_LABEL_T:
2683 // Only add a line if this line is unselected; if the label and line are both
2684 // selected they'll move together
2685 if( line->HitTest( aPoint, 1 ) && !line->HasFlag( SELECTED )
2686 && !line->HasFlag( SELECTED_BY_DRAG ) )
2687 {
2688 newWire = makeNewWire( aCommit, line, aSelectedItem, aPoint, aPoint );
2689 newWire->SetFlags( SELECTED_BY_DRAG | STARTPOINT );
2690 newWire->StoreAngle( ( line->Angle() + ANGLE_90 ).Normalize() );
2691 aList.push_back( newWire );
2692
2693 if( aPoint != line->GetStartPoint() && aPoint != line->GetEndPoint() )
2694 {
2695 // Split line in half
2696 aCommit->Modify( line, m_frame->GetScreen() );
2697
2698 VECTOR2I oldEnd = line->GetEndPoint();
2699 line->SetEndPoint( aPoint );
2700
2701 makeNewWire( aCommit, line, line, aPoint, oldEnd );
2702 makeNewJunction( aCommit, line, aPoint );
2703 }
2704 else
2705 {
2706 m_lineConnectionCache[ newWire ] = { line };
2707 m_lineConnectionCache[ line ] = { newWire };
2708 }
2709 }
2710 break;
2711
2712 default:
2713 break;
2714 }
2715
2716 break;
2717 }
2718
2719 // When only one end moves, keep attached labels tracking the moving end so they stay
2720 // connected to the line.
2721 for( SCH_ITEM* item : items.Overlapping( line->GetBoundingBox() ) )
2722 {
2723 SCH_LABEL_BASE* label = dynamic_cast<SCH_LABEL_BASE*>( item );
2724
2725 if( !label || label->IsSelected() )
2726 continue; // These will be moved on their own because they're selected
2727
2728 if( label->HasFlag( SELECTED_BY_DRAG ) )
2729 continue;
2730
2731 if( label->CanConnect( line ) && line->HitTest( label->GetPosition(), 1 ) )
2732 {
2733 label->SetFlags( SELECTED_BY_DRAG );
2734 aList.push_back( label );
2735
2737 info.attachedLine = line;
2738 info.originalLabelPos = label->GetPosition();
2739 info.originalLineStart = line->GetStartPoint();
2740 info.originalLineEnd = line->GetEndPoint();
2741 m_specialCaseLabels[label] = info;
2742 }
2743 }
2744
2745 break;
2746 }
2747
2748 case SCH_SHEET_T:
2749 for( SCH_SHEET_PIN* pin : static_cast<SCH_SHEET*>( test )->GetPins() )
2750 {
2751 if( pin->IsConnected( aPoint ) )
2752 {
2753 if( pin->IsSelected() && aSelectedItem->Type() == SCH_LINE_T )
2754 {
2755 SCH_LINE* line = static_cast<SCH_LINE*>( aSelectedItem );
2756 m_specialCaseSheetPins[ pin ] = { line, line->GetStartPoint() == aPoint };
2757 }
2758 else if( !newWire )
2759 {
2760 // Add a new wire between the sheetpin and the selected item so the
2761 // selected item can be dragged.
2762 newWire = makeNewWire( aCommit, pin, aSelectedItem, aPoint, aPoint );
2763 newWire->SetFlags( SELECTED_BY_DRAG | STARTPOINT );
2764 aList.push_back( newWire );
2765 }
2766 }
2767 }
2768
2769 break;
2770
2771 case SCH_SYMBOL_T:
2772 case SCH_JUNCTION_T:
2773 if( test->IsConnected( aPoint ) && !newWire )
2774 {
2775 // Add a new wire between the symbol or junction and the selected item so
2776 // the selected item can be dragged.
2777 newWire = makeNewWire( aCommit, test, aSelectedItem, aPoint, aPoint );
2778 newWire->SetFlags( SELECTED_BY_DRAG | STARTPOINT );
2779 aList.push_back( newWire );
2780 }
2781
2782 break;
2783
2784 case SCH_NO_CONNECT_T:
2785 // Select no-connects that are connected to items being moved.
2786 if( !test->HasFlag( SELECTED_BY_DRAG ) && test->IsConnected( aPoint ) )
2787 {
2788 aList.push_back( test );
2789 test->SetFlags( SELECTED_BY_DRAG );
2790 }
2791
2792 break;
2793
2794 case SCH_LABEL_T:
2795 case SCH_GLOBAL_LABEL_T:
2796 case SCH_HIER_LABEL_T:
2798 case SCH_SHEET_PIN_T:
2799 // Performance optimization:
2800 if( test->HasFlag( SELECTED_BY_DRAG ) )
2801 break;
2802
2803 // Select labels that are connected to a wire (or bus) being moved.
2804 if( aSelectedItem->Type() == SCH_LINE_T && test->CanConnect( aSelectedItem ) )
2805 {
2806 SCH_LABEL_BASE* label = static_cast<SCH_LABEL_BASE*>( test );
2807 SCH_LINE* line = static_cast<SCH_LINE*>( aSelectedItem );
2808
2809 bool oneEndFixed = !line->HasFlag( STARTPOINT ) || !line->HasFlag( ENDPOINT );
2810
2811 if( line->HitTest( label->GetTextPos(), 1 ) )
2812 {
2813 if( ( !line->HasFlag( STARTPOINT ) && label->GetPosition() == line->GetStartPoint() )
2814 || ( !line->HasFlag( ENDPOINT ) && label->GetPosition() == line->GetEndPoint() ) )
2815 {
2816 //If we have a line selected at only one end, don't grab labels
2817 //connected directly to the unselected endpoint
2818 break;
2819 }
2820 else
2821 {
2822 label->SetFlags( SELECTED_BY_DRAG );
2823 aList.push_back( label );
2824
2825 if( oneEndFixed )
2826 {
2828 info.attachedLine = line;
2829 info.originalLabelPos = label->GetPosition();
2830 info.originalLineStart = line->GetStartPoint();
2831 info.originalLineEnd = line->GetEndPoint();
2832 m_specialCaseLabels[label] = info;
2833 }
2834 }
2835 }
2836 }
2837 else if( test->IsConnected( aPoint ) && !newWire )
2838 {
2839 // Add a new wire between the label and the selected item so the selected item
2840 // can be dragged.
2841 newWire = makeNewWire( aCommit, test, aSelectedItem, aPoint, aPoint );
2842 newWire->SetFlags( SELECTED_BY_DRAG | STARTPOINT );
2843 aList.push_back( newWire );
2844 }
2845
2846 break;
2847
2850 // Performance optimization:
2851 if( test->HasFlag( SELECTED_BY_DRAG ) )
2852 break;
2853
2854 // Select bus entries that are connected to a bus being moved.
2855 if( aSelectedItem->Type() == SCH_LINE_T && test->CanConnect( aSelectedItem ) )
2856 {
2857 SCH_LINE* line = static_cast<SCH_LINE*>( aSelectedItem );
2858
2859 if( ( !line->HasFlag( STARTPOINT ) && test->IsConnected( line->GetStartPoint() ) )
2860 || ( !line->HasFlag( ENDPOINT ) && test->IsConnected( line->GetEndPoint() ) ) )
2861 {
2862 // If we have a line selected at only one end, don't grab bus entries
2863 // connected directly to the unselected endpoint
2864 continue;
2865 }
2866
2867 for( VECTOR2I& point : test->GetConnectionPoints() )
2868 {
2869 if( line->HitTest( point, 1 ) )
2870 {
2871 test->SetFlags( SELECTED_BY_DRAG );
2872 aList.push_back( test );
2873
2874 // A bus entry needs its wire & label as well
2875 std::vector<VECTOR2I> ends = test->GetConnectionPoints();
2876 VECTOR2I otherEnd;
2877
2878 if( ends[0] == point )
2879 otherEnd = ends[1];
2880 else
2881 otherEnd = ends[0];
2882
2883 getConnectedDragItems( aCommit, test, otherEnd, aList );
2884
2885 // No need to test the other end of the bus entry
2886 break;
2887 }
2888 }
2889 }
2890
2891 break;
2892
2893 default:
2894 break;
2895 }
2896 }
2897}
2898
2899
2900void SCH_MOVE_TOOL::moveItem( EDA_ITEM* aItem, const VECTOR2I& aDelta )
2901{
2902 static int moveCallCount = 0;
2903 wxLogTrace( traceSchMove, "moveItem[%d]: type=%d, delta=(%d,%d)",
2904 ++moveCallCount, aItem->Type(), aDelta.x, aDelta.y );
2905
2906 switch( aItem->Type() )
2907 {
2908 case SCH_LINE_T:
2909 if( m_mode == MOVE )
2910 {
2911 // In MOVE mode, both endpoints always move
2912 static_cast<SCH_LINE*>( aItem )->Move( aDelta );
2913 }
2914 else
2915 {
2916 // In DRAG mode, only flagged endpoints move - use shared function
2917 MoveSchematicItem( aItem, aDelta );
2918 }
2919
2920 break;
2921
2922 case SCH_PIN_T:
2923 case SCH_FIELD_T:
2924 {
2925 SCH_ITEM* parent = (SCH_ITEM*) aItem->GetParent();
2926 VECTOR2I delta( aDelta );
2927
2928 if( parent && parent->Type() == SCH_SYMBOL_T )
2929 {
2930 SCH_SYMBOL* symbol = (SCH_SYMBOL*) aItem->GetParent();
2931 TRANSFORM transform = symbol->GetTransform().InverseTransform();
2932
2933 delta = transform.TransformCoordinate( delta );
2934 }
2935
2936 static_cast<SCH_ITEM*>( aItem )->Move( delta );
2937
2938 // If we're moving a field with respect to its parent then it's no longer auto-placed
2939 if( aItem->Type() == SCH_FIELD_T && parent && !parent->IsSelected() )
2941
2942 break;
2943 }
2944
2945 case SCH_SHEET_PIN_T:
2946 // Use shared function for sheet pin movement
2947 MoveSchematicItem( aItem, aDelta );
2948 break;
2949
2950 case SCH_LABEL_T:
2952 case SCH_GLOBAL_LABEL_T:
2953 case SCH_HIER_LABEL_T:
2954 {
2955 SCH_LABEL_BASE* label = static_cast<SCH_LABEL_BASE*>( aItem );
2956
2957 // Interactive dragging positions attached labels separately along their wire.
2958 // Align to Grid has no such pass and must move them with the endpoint.
2959 if( !m_inMoveTool || !m_specialCaseLabels.count( label ) )
2960 label->Move( aDelta );
2961
2962 break;
2963 }
2964
2965 default:
2966 static_cast<SCH_ITEM*>( aItem )->Move( aDelta );
2967 break;
2968 }
2969
2970 aItem->SetFlags( IS_MOVING );
2971}
2972
2973
2975{
2977 SCH_SELECTION& selection = m_selectionTool->RequestSelection( SCH_COLLECTOR::MovableItems );
2978
2979 m_selectionTool->FilterSelectionForLockedItems();
2980
2981 GRID_HELPER_GRIDS selectionGrid = grid.GetSelectionGrid( selection );
2982 SCH_COMMIT commit( m_toolMgr );
2983
2984 auto doMoveItem =
2985 [&]( EDA_ITEM* item, const VECTOR2I& delta )
2986 {
2987 commit.Modify( item, m_frame->GetScreen(), RECURSE_MODE::RECURSE );
2988
2989 // Ensure only one end is moved when calling moveItem
2990 // i.e. we are in drag mode
2991 MOVE_MODE tmpMode = m_mode;
2992 m_mode = DRAG;
2993 moveItem( item, delta );
2994 m_mode = tmpMode;
2995
2996 item->ClearFlags( IS_MOVING );
2997 updateItem( item, true );
2998 };
2999
3000 for( SCH_ITEM* it : m_frame->GetScreen()->Items() )
3001 {
3002 if( !it->IsSelected() )
3003 it->ClearFlags( STARTPOINT | ENDPOINT );
3004
3005 if( !selection.IsHover() && it->IsSelected() )
3006 it->SetFlags( STARTPOINT | ENDPOINT );
3007
3008 it->SetStoredPos( it->GetPosition() );
3009
3010 if( it->Type() == SCH_SHEET_T )
3011 {
3012 for( SCH_SHEET_PIN* pin : static_cast<SCH_SHEET*>( it )->GetPins() )
3013 pin->SetStoredPos( pin->GetPosition() );
3014 }
3015 }
3016
3017 SCH_ALIGNMENT_CALLBACKS callbacks;
3018
3019 callbacks.m_doMoveItem = doMoveItem;
3020
3021 callbacks.m_getConnectedDragItems =
3022 [&]( SCH_ITEM* aItem, const VECTOR2I& aPoint, EDA_ITEMS& aList )
3023 {
3024 getConnectedDragItems( &commit, aItem, aPoint, aList );
3025 };
3026
3027 callbacks.m_updateItem =
3028 [&]( EDA_ITEM* aItem )
3029 {
3030 updateItem( aItem, true );
3031 };
3032
3033 recordRedundantJunctions( selection );
3034
3035 std::vector<EDA_ITEM*> items( selection.begin(), selection.end() );
3036 AlignSchematicItemsToGrid( m_frame->GetScreen(), items, grid, selectionGrid, callbacks );
3037
3039 lwbTool->TrimOverLappingWires( &commit, &selection );
3040 migrateHiddenJunctions( &commit );
3041 lwbTool->AddJunctionsIfNeeded( &commit, &selection );
3042
3044
3045 m_frame->Schematic().CleanUp( &commit );
3046 commit.Push( _( "Align Items to Grid" ) );
3047 return 0;
3048}
3049
3050
3052{
3053 // Remove new bend lines added during the drag
3054 for( SCH_LINE* newLine : m_newDragLines )
3055 {
3056 m_frame->RemoveFromScreen( newLine, m_frame->GetScreen() );
3057 delete newLine;
3058 }
3059
3060 m_newDragLines.clear();
3061}
3062
3063
constexpr EDA_IU_SCALE schIUScale
Definition base_units.h:130
BOX2< VECTOR2I > BOX2I
Definition box2.h:914
static TOOL_ACTION cursorDblClick
Definition actions.h:177
@ CURSOR_RIGHT
Definition actions.h:311
@ CURSOR_LEFT
Definition actions.h:309
@ CURSOR_UP
Definition actions.h:305
@ CURSOR_DOWN
Definition actions.h:307
static TOOL_ACTION undo
Definition actions.h:71
static TOOL_ACTION duplicate
Definition actions.h:80
static TOOL_ACTION doDelete
Definition actions.h:81
static TOOL_ACTION cursorClick
Definition actions.h:176
static TOOL_ACTION redo
Definition actions.h:72
static TOOL_ACTION increment
Definition actions.h:90
static TOOL_ACTION selectionClear
Clear the current selection.
Definition actions.h:220
static TOOL_ACTION refreshPreview
Definition actions.h:155
static const ADVANCED_CFG & GetCfg()
Get the singleton instance's config, which is shared by all consumers.
constexpr const Vec & GetPosition() const
Definition box2.h:208
constexpr coord_type GetY() const
Definition box2.h:205
constexpr size_type GetWidth() const
Definition box2.h:211
constexpr coord_type GetX() const
Definition box2.h:204
constexpr BOX2< Vec > & Merge(const BOX2< Vec > &aRect)
Modify the position and size of the rectangle in order to contain aRect.
Definition box2.h:594
constexpr size_type GetHeight() const
Definition box2.h:212
constexpr bool Contains(const Vec &aPoint) const
Definition box2.h:165
COMMIT & Added(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr)
Notify observers that aItem has been added.
Definition commit.h:80
COMMIT & Modify(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr, RECURSE_MODE aRecurse=RECURSE_MODE::NO_RECURSE)
Modify a given item in the model.
Definition commit.h:102
COMMIT & Removed(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr)
Notify observers that aItem has been removed.
Definition commit.h:92
void AddItem(const TOOL_ACTION &aAction, const SELECTION_CONDITION &aCondition, int aOrder=ANY_ORDER)
Add a menu entry to run a TOOL_ACTION on selected items.
static void sort_dangling_end_items(std::vector< DANGLING_END_ITEM > &aItemListByType, std::vector< DANGLING_END_ITEM > &aItemListByPos)
Helper class used to store the state of schematic items that can be connected to other schematic item...
Definition sch_item.h:101
A base class for most all the KiCad significant classes used in schematics and boards.
Definition eda_item.h:98
virtual VECTOR2I GetPosition() const
Definition eda_item.h:348
virtual void ClearEditFlags()
Definition eda_item.h:178
virtual const BOX2I GetBoundingBox() const
Return the orthogonal bounding box of this object for display purposes.
Definition eda_item.cpp:270
void SetFlags(EDA_ITEM_FLAGS aMask)
Definition eda_item.h:158
const KIID m_Uuid
Definition eda_item.h:599
virtual EDA_GROUP * GetParentGroup() const
Definition eda_item.h:116
KICAD_T Type() const
Returns the type of object.
Definition eda_item.h:110
void ClearFlags(EDA_ITEM_FLAGS aMask=EDA_ITEM_ALL_FLAGS)
Definition eda_item.h:160
bool IsSelected() const
Definition eda_item.h:134
EDA_ITEM * GetParent() const
Definition eda_item.h:112
bool HasSelectedAncestorGroup() const
Definition eda_item.cpp:241
bool HasFlag(EDA_ITEM_FLAGS aFlag) const
Definition eda_item.h:168
bool IsNew() const
Definition eda_item.h:131
virtual VECTOR2I GetTextPos() const
Definition eda_text.h:313
Implement an R-tree for fast spatial and type indexing of schematic items.
Definition sch_rtree.h:37
EE_TYPE Overlapping(const BOX2I &aRect) const
Definition sch_rtree.h:253
static const TOOL_EVENT SelectedItemsMoved
Selected items were moved, this can be very high frequency on the canvas, use with care.
Definition actions.h:355
An interface for classes handling user events controlling the view behavior such as zooming,...
virtual void ForceCursorPosition(bool aEnabled, const VECTOR2D &aPosition=VECTOR2D(0, 0))
Place the cursor immediately at a given point.
virtual void ShowCursor(bool aEnabled)
Enable or disables display of cursor.
VECTOR2D GetCursorPosition() const
Return the current cursor position in world coordinates.
virtual void SetCursorPosition(const VECTOR2D &aPosition, bool aWarpView=true, bool aTriggeredByArrows=false, long aArrowCommand=0)=0
Move cursor to the requested position expressed in world coordinates.
virtual void SetAutoPan(bool aEnabled)
Turn on/off auto panning (this feature is used when there is a tool active (eg.
const VC_SETTINGS & GetSettings() const
Return the current VIEW_CONTROLS settings.
static TOOL_ACTION rotateCCW
static TOOL_ACTION breakWire
static TOOL_ACTION toText
static TOOL_ACTION restartMove
static TOOL_ACTION toHLabel
static TOOL_ACTION rotateCW
static TOOL_ACTION drag
static TOOL_ACTION toLabel
static TOOL_ACTION alignToGrid
static TOOL_ACTION toDLabel
static TOOL_ACTION slice
static TOOL_ACTION toTextBox
static TOOL_ACTION highlightNet
static TOOL_ACTION repeatDrawItem
static TOOL_ACTION toGLabel
static TOOL_ACTION selectOnPCB
static TOOL_ACTION move
static const std::vector< KICAD_T > MovableItems
Commit for the schematic and symbol editors.
Definition sch_commit.h:54
COMMIT & Stage(EDA_ITEM *aItem, CHANGE_TYPE aChangeType, BASE_SCREEN *aScreen=nullptr, RECURSE_MODE aRecurse=RECURSE_MODE::NO_RECURSE) override
Add a change of the item aItem of type aChangeType to the change list.
void Revert() override
Revert the commit by restoring the modified items state.
void Push(const wxString &aMessage=wxEmptyString, int aCommitFlags=0) override
Execute the changes.
Each graphical item can have a SCH_CONNECTION describing its logical connection (to a bus or net).
void Clone(const SCH_CONNECTION &aOther)
Copies connectivity information (but not parent) from another connection.
static bool IsBusLabel(const wxString &aLabel)
Test if aLabel has a bus notation.
Schematic editor (Eeschema) main window.
SCH_SHEET_PATH & GetCurrentSheet() const
A set of SCH_ITEMs (i.e., without duplicates).
Definition sch_group.h:48
Base class for any item which can be embedded within the SCHEMATIC container class,...
Definition sch_item.h:170
void SetStoredPos(const VECTOR2I &aPos)
Definition sch_item.h:315
virtual bool CanConnect(const SCH_ITEM *aItem) const
Definition sch_item.h:540
virtual void RunOnChildren(const std::function< void(SCH_ITEM *)> &aFunction, RECURSE_MODE aMode)
Definition sch_item.h:678
int GetBodyStyle() const
Definition sch_item.h:253
SCH_CONNECTION * InitializeConnection(const SCH_SHEET_PATH &aPath, CONNECTION_GRAPH *aGraph)
Create a new connection object associated with this object.
Definition sch_item.cpp:790
virtual void Move(const VECTOR2I &aMoveVector)
Move the item by aMoveVector to a new position.
Definition sch_item.h:417
void SetLayer(SCH_LAYER_ID aLayer)
Definition sch_item.h:354
void SetConnectivityDirty(bool aDirty=true)
Set the dirty flag.
Definition sch_item.cpp:314
void SetFieldsAutoplaced(AUTOPLACE_ALGO aAlgo)
Definition sch_item.h:674
bool IsConnected(const VECTOR2I &aPoint) const
Test the item to see if it is connected to aPoint.
Definition sch_item.cpp:571
virtual bool IsMovableFromAnchorPoint() const
Check if object is movable from the anchor point.
Definition sch_item.h:312
SCH_CONNECTION * Connection(const SCH_SHEET_PATH *aSheet=nullptr) const
Retrieve the connection associated with this object in the given sheet.
Definition sch_item.cpp:580
VECTOR2I & GetStoredPos()
Definition sch_item.h:314
bool IsGroupableType() const
Definition sch_item.cpp:144
virtual std::vector< VECTOR2I > GetConnectionPoints() const
Add all the connection points for this item to aPoints.
Definition sch_item.h:558
void SetLastResolvedState(const SCH_ITEM *aItem) override
void Move(const VECTOR2I &aMoveVector) override
Move the item by aMoveVector to a new position.
bool CanConnect(const SCH_ITEM *aItem) const override
Definition sch_label.h:146
Tool responsible for drawing/placing items (symbols, wires, buses, labels, etc.)
int AddJunctionsIfNeeded(SCH_COMMIT *aCommit, SCH_SELECTION *aSelection)
Handle the addition of junctions to a selection of objects.
SCH_JUNCTION * AddJunction(SCH_COMMIT *aCommit, SCH_SCREEN *aScreen, const VECTOR2I &aPos)
int TrimOverLappingWires(SCH_COMMIT *aCommit, SCH_SELECTION *aSelection)
Logic to remove wires when overlapping correct items.
static bool IsDrawingLineWireOrBus(const SELECTION &aSelection)
void BreakSegment(SCH_COMMIT *aCommit, SCH_LINE *aSegment, const VECTOR2I &aPoint, SCH_LINE **aNewSegment, SCH_SCREEN *aScreen)
Break a single segment into two at the specified point.
Segment description base class to describe items which have 2 end points (track, wire,...
Definition sch_line.h:39
bool HitTest(const VECTOR2I &aPosition, int aAccuracy=0) const override
Test if aPosition is inside or on the boundary of this item.
Definition sch_line.cpp:908
void StoreAngle()
Save the current line angle.
Definition sch_line.h:112
std::vector< VECTOR2I > GetConnectionPoints() const override
Add all the connection points for this item to aPoints.
Definition sch_line.cpp:808
bool IsStartDangling() const
Definition sch_line.h:334
const BOX2I GetBoundingBox() const override
Return the orthogonal bounding box of this object for display purposes.
Definition sch_line.cpp:315
EDA_ANGLE Angle() const
Get the angle between the start and end lines.
Definition sch_line.h:101
VECTOR2I GetEndPoint() const
Definition sch_line.h:145
VECTOR2I GetStartPoint() const
Definition sch_line.h:136
bool IsEndDangling() const
Definition sch_line.h:335
void MoveEnd(const VECTOR2I &aMoveVector)
Definition sch_line.cpp:260
void SetLastResolvedState(const SCH_ITEM *aItem) override
Definition sch_line.h:160
void MoveStart(const VECTOR2I &aMoveVector)
Definition sch_line.cpp:254
double GetLength() const
Definition sch_line.cpp:340
void SetEndPoint(const VECTOR2I &aPosition)
Definition sch_line.h:146
bool IsDangling() const override
Definition sch_line.h:336
void moveSelectionToSheet(SCH_SELECTION &aSelection, SCH_SHEET *aTarget, SCH_COMMIT *aCommit)
void refreshSelectionTraits(const SCH_SELECTION &aSelection, bool &aHasSheetPins, bool &aHasGraphicItems, bool &aHasNonGraphicItems, bool &aIsGraphicsOnly)
Refresh selection traits (sheet pins, graphic items, etc.)
bool Init() override
Init() is called once upon a registration of the tool.
VECTOR2I m_cursor
Last cursor position (needed for getModificationPoint() to avoid changes of edit reference point).
void trimDanglingLines(SCH_COMMIT *aCommit)
Cleanup dangling lines left after a drag.
void orthoLineDrag(SCH_COMMIT *aCommit, SCH_LINE *line, const VECTOR2I &splitDelta, int &xBendCount, int &yBendCount, const EE_GRID_HELPER &grid)
std::unordered_set< SCH_LINE * > m_newDragLines
Lines added at bend points dynamically during the move.
SCH_SHEET * findTargetSheet(const SCH_SELECTION &aSelection, const VECTOR2I &aCursorPos, bool aHasSheetPins, bool aIsGraphicsOnly, bool aCtrlDown)
Find the target sheet for dropping items (if any)
bool handleMoveToolActions(const TOOL_EVENT *aEvent, SCH_COMMIT *aCommit, const SCH_SELECTION &aSelection)
Handle tool action events during the move operation.
OPT_VECTOR2I m_anchorPos
void performItemMove(SCH_SELECTION &aSelection, const VECTOR2I &aDelta, SCH_COMMIT *aCommit, int &aXBendCount, int &aYBendCount, const EE_GRID_HELPER &aGrid)
Perform the actual move of items by delta, handling split moves and orthogonal dragging.
int Main(const TOOL_EVENT &aEvent)
Run an interactive move of the selected items, or the item under the cursor.
SCH_SELECTION & prepareSelection(bool &aUnselect)
Promote pin selections to parent symbols and request final selection.
void prepareMoveOperation(SCH_SELECTION &aSelection, SCH_COMMIT *aCommit, std::vector< DANGLING_END_ITEM > &aInternalPoints, GRID_HELPER_GRIDS &aSnapLayer)
Set up flags, connections, and undo state for either a new or restarted move.
void spreadMovingSheetPinGroups(const SCH_SELECTION &aSelection)
Slide a group of dragged sheet pins along the sheet border so they keep their spacing.
void setupItemsForMove(SCH_SELECTION &aSelection, std::vector< DANGLING_END_ITEM > &aInternalPoints)
Setup items for move operation, marking dangling ends.
bool m_inMoveTool
Re-entrancy guard.
std::vector< KIID > m_dragAdditions
Items (such as wires) which were added to the selection for a drag.
void moveItem(EDA_ITEM *aItem, const VECTOR2I &aDelta)
void setupItemsForDrag(SCH_SELECTION &aSelection, SCH_COMMIT *aCommit)
Setup items for drag operation, collecting connected items.
void restartMoveOperation(const TOOL_EVENT &aEvent, SCH_SELECTION &aSelection, SCH_COMMIT *aCommit, std::vector< DANGLING_END_ITEM > &aInternalPoints, GRID_HELPER_GRIDS &aSnapLayer)
Restore the original geometry and restart with the requested move mode.
std::unordered_set< SCH_LINE * > m_changedDragLines
Lines changed by drag algorithm that weren't selected.
void Reset(RESET_REASON aReason) override
Bring the tool to a known, initial state.
void finalizeMoveOperation(SCH_SELECTION &aSelection, SCH_COMMIT *aCommit, bool aUnselect, const std::vector< DANGLING_END_ITEM > &aInternalPoints)
Finalize the move operation, updating junctions and cleaning up.
void setTransitions() override
Set up handlers for various events.
void getConnectedItems(SCH_ITEM *aOriginalItem, const VECTOR2I &aPoint, EDA_ITEMS &aList)
Find additional items for a drag operation.
std::map< SCH_LINE *, EDA_ITEMS > m_lineConnectionCache
Cache of the line's original connections before dragging started.
OPT_VECTOR2I m_breakPos
std::map< SCH_SHEET_PIN *, long long > m_sheetPinDragArc
void updateStoredPositions(const SCH_SELECTION &aSelection)
Update stored positions after transformations (rotation, mirroring, etc.) during move.
std::vector< HIDDEN_JUNCTION > m_hiddenJunctions
bool doMoveSelection(const TOOL_EVENT &aEvent, SCH_COMMIT *aCommit)
void recordRedundantJunctions(SCH_SELECTION &aSelection)
Hide the junction dots that the pending edit will make redundant, noting the line end each one marks.
bool m_moveInProgress
Flag determining if anything is being dragged right now.
bool dropWouldRecurse(const SCH_SELECTION &aSelection, const SCH_SHEET *aTargetSheet)
True when dropping the selection into aTargetSheet would make a sheet its own descendant.
void getConnectedDragItems(SCH_COMMIT *aCommit, SCH_ITEM *fixed, const VECTOR2I &selected, EDA_ITEMS &aList)
VECTOR2I m_moveOffset
std::map< SCH_LABEL_BASE *, SPECIAL_CASE_LABEL_INFO > m_specialCaseLabels
void migrateHiddenJunctions(SCH_COMMIT *aCommit)
Move those junction dots to wherever the line end they marked has ended up.
int AlignToGrid(const TOOL_EVENT &aEvent)
Align selected elements to the grid.
void clearNewDragLines()
Clears the new drag lines and removes them from the screen.
MOVE_MODE m_mode
void preprocessBreakOrSliceSelection(SCH_COMMIT *aCommit, const TOOL_EVENT &aEvent)
Break or slice the current selection before initiating a move, if required.
void startMoveOperation(SCH_SELECTION &aSelection, SCH_COMMIT *aCommit, std::vector< DANGLING_END_ITEM > &aInternalPoints, GRID_HELPER_GRIDS &aSnapLayer)
Start a new move and position the selection at its initial cursor anchor.
std::map< SCH_SHEET_PIN *, std::pair< SCH_LINE *, bool > > m_specialCaseSheetPins
void Append(SCH_ITEM *aItem, bool aUpdateLibSymbol=true)
bool IsExplicitJunction(const VECTOR2I &aPosition) const
Indicate that a junction dot is necessary at the given location.
EE_RTREE & Items()
Get the full RTree, usually for iterating.
Definition sch_screen.h:122
const wxString & GetFileName() const
Definition sch_screen.h:157
bool IsExplicitJunctionNeeded(const VECTOR2I &aPosition) const
Indicate that a junction dot is necessary at the given location, and does not yet exist.
A container for handling SCH_SHEET_PATH objects in a flattened hierarchy.
bool TestForRecursion(const SCH_SHEET_LIST &aSrcSheetHierarchy, const wxString &aDestFileName)
Test every SCH_SHEET_PATH in this SCH_SHEET_LIST to verify if adding the sheets stored in aSrcSheetHi...
Handle access to a stack of flattened SCH_SHEET objects by way of a path for creating a flattened sch...
Define a sheet pin (label) used in sheets to create hierarchical schematics.
SHEET_SIDE GetSide() const
Sheet symbol placed in a schematic, and is the entry point for a sub schematic.
Definition sch_sheet.h:48
bool IsTopLevelSheet() const
Check if this sheet is a top-level sheet.
SCH_SHEET_PIN * GetPin(const VECTOR2I &aPosition)
Return the sheet pin item found at aPosition in the sheet.
VECTOR2I GetSize() const
Definition sch_sheet.h:147
SCH_SCREEN * GetScreen() const
Definition sch_sheet.h:145
VECTOR2I GetPosition() const override
Definition sch_sheet.h:506
const BOX2I GetBodyBoundingBox() const
Return a bounding box for the sheet body but not the fields.
std::vector< SCH_SHEET_PIN * > & GetPins()
Definition sch_sheet.h:243
Schematic symbol object.
Definition sch_symbol.h:73
VECTOR2I GetPosition() const override
Definition sch_text.h:143
void updateItem(EDA_ITEM *aItem, bool aUpdateRTree) const
bool Init() override
Init() is called once upon a registration of the tool.
void Reset(RESET_REASON aReason) override
Bring the tool to a known, initial state.
SCH_TOOL_BASE(const std::string &aName)
SCH_SELECTION_TOOL * m_selectionTool
Definition seg.h:38
const VECTOR2I NearestPoint(const VECTOR2I &aP) const
Compute a point on the segment (this) that is closest to point aP.
Definition seg.cpp:599
static SELECTION_CONDITION OnlyTypes(std::vector< KICAD_T > aTypes)
Create a functor that tests if the selected items are only of given types.
virtual void Add(EDA_ITEM *aItem)
A null aItem is ignored; the selection never holds null members.
Definition selection.cpp:38
ITER end()
Definition selection.h:76
ITER begin()
Definition selection.h:75
VECTOR2I GetReferencePoint() const
bool IsHover() const
Definition selection.h:85
virtual unsigned int GetSize() const override
Return the number of stored items.
Definition selection.h:104
EDA_ITEM * Front() const
Definition selection.h:176
int Size() const
Returns the number of selected parts.
Definition selection.h:120
void SetReferencePoint(const VECTOR2I &aP)
bool Empty() const
Checks if there is anything selected.
Definition selection.h:114
std::vector< EDA_ITEM * > GetItemsSortedByTypeAndXY(bool leftBeforeRight=true, bool topBeforeBottom=true) const
Returns a copy of this selection of items sorted by their X then Y position.
bool HasReferencePoint() const
Definition selection.h:215
const TRANSFORM & GetTransform() const
Definition symbol.h:247
KIGFX::VIEW_CONTROLS * getViewControls() const
Definition tool_base.cpp:40
KIGFX::VIEW * getView() const
Definition tool_base.cpp:34
Generic, UI-independent tool event.
Definition tool_event.h:167
bool DisableGridSnapping() const
Definition tool_event.h:367
bool HasParameter() const
Definition tool_event.h:460
bool IsCancelInteractive() const
Indicate the event should restart/end an ongoing interactive tool's event loop (eg esc key,...
TOOL_ACTIONS Action() const
Returns more specific information about the type of an event.
Definition tool_event.h:246
bool IsActivate() const
Definition tool_event.h:341
COMMIT * Commit() const
Definition tool_event.h:279
bool IsClick(int aButtonMask=BUT_ANY) const
TOOL_EVENT_CATEGORY Category() const
Return the category (eg. mouse/keyboard/action) of an event.
Definition tool_event.h:243
bool IsDrag(int aButtonMask=BUT_ANY) const
Definition tool_event.h:311
int Modifier(int aMask=MD_MODIFIER_MASK) const
Return information about key modifiers state (Ctrl, Alt, etc.).
Definition tool_event.h:362
bool IsAction(const TOOL_ACTION *aAction) const
Test if the event contains an action issued upon activation of the given TOOL_ACTION.
T Parameter() const
Return a parameter assigned to the event.
Definition tool_event.h:484
bool IsDblClick(int aButtonMask=BUT_ANY) const
std::atomic< SYNCRONOUS_TOOL_STATE > * SynchronousState() const
Definition tool_event.h:276
std::optional< int > GetCommandId() const
Definition tool_event.h:544
void SetPassEvent(bool aPass=true)
Definition tool_event.h:252
bool IsMouseUp(int aButtonMask=BUT_ANY) const
Definition tool_event.h:321
bool IsMotion() const
Definition tool_event.h:326
const std::string Format() const
Return information about event in form of a human-readable string.
void Go(int(SCH_EDIT_FRAME::*aStateFunc)(const TOOL_EVENT &), const TOOL_EVENT_LIST &aConditions=TOOL_EVENT(TC_ANY, TA_ANY))
std::unique_ptr< TOOL_MENU > m_menu
TOOL_EVENT * Wait(const TOOL_EVENT_LIST &aEventList=TOOL_EVENT(TC_ANY, TA_ANY))
for transforming drawing coordinates for a wxDC device context.
Definition transform.h:42
TRANSFORM InverseTransform() const
Calculate the Inverse mirror/rotation transform.
Definition transform.cpp:55
VECTOR2I TransformCoordinate(const VECTOR2I &aPoint) const
Calculate a new coordinate according to the mirror/rotation transform.
Definition transform.cpp:40
@ CHT_REMOVE
Definition commit.h:39
@ CHT_DONE
Flag to indicate the change is already applied.
Definition commit.h:43
@ CHT_ADD
Definition commit.h:38
KICURSOR
Definition cursors.h:40
@ PLACE
Definition cursors.h:94
@ MOVING
Definition cursors.h:44
#define _(s)
static constexpr EDA_ANGLE ANGLE_90
Definition eda_angle.h:450
@ NONE
Definition eda_fill.h:42
@ RECURSE
Definition eda_item.h:51
@ NO_RECURSE
Definition eda_item.h:52
#define IS_PASTED
Modifier on IS_NEW which indicates it came from clipboard.
#define IS_CHANGED
Item was edited, and modified.
#define BRIGHTENED
item is drawn with a bright contour
#define IS_NEW
New item, just created.
#define SELECTED
Item was manually selected by the user.
#define SELECTED_BY_DRAG
Item was algorithmically selected as a dragged item.
#define IS_BROKEN
Is a segment just broken by BreakSegment.
#define STRUCT_DELETED
flag indication structures to be erased
#define ENDPOINT
ends. (Used to support dragging.)
#define IS_MOVING
Item being moved.
#define STARTPOINT
When a line is selected, these flags indicate which.
@ ID_POPUP_SCH_SELECT_UNIT
Definition eeschema_id.h:81
@ ID_POPUP_SCH_SELECT_BODY_STYLE
Definition eeschema_id.h:91
@ ID_POPUP_SCH_SELECT_BODY_STYLE_END
Definition eeschema_id.h:93
@ ID_POPUP_SCH_SELECT_UNIT_END
Definition eeschema_id.h:85
@ LINE_MODE_FREE
GRID_HELPER_GRIDS
Definition grid_helper.h:55
@ GRID_CURRENT
Definition grid_helper.h:57
bool m_ConnectivityEngine
Use the schematic connectivity engine instead of CONNECTION_GRAPH.
const wxChar *const traceSchMove
Flag to watch how schematic move tool actions are handled.
@ LAYER_WIRE
Definition layer_ids.h:474
@ LAYER_BUS
Definition layer_ids.h:475
@ LAYER_BUS_JUNCTION
Definition layer_ids.h:520
std::vector< SCH_JUNCTION * > PreviewJunctions(const SCH_SCREEN *aScreen, const std::vector< class SCH_ITEM * > &aItems)
Determine the points where explicit junctions would be required if the given temporary items were com...
POINT_INFO AnalyzePoint(const EE_RTREE &aItem, const VECTOR2I &aPosition, bool aBreakCrossings)
Check a tree of items for a confluence at a given point and work out what kind of junction it is,...
@ REPAINT
Item needs to be redrawn.
Definition view_item.h:54
bool signbit(T v)
Integral version of std::signbit that works all compilers.
Definition kicad_algo.h:172
@ VERTICAL
A segment (or two points) is vertical.
@ HORIZONTAL
A segment (or two points) is horizontal.
see class PGM_BASE
Class to handle a set of SCH_ITEMs.
@ AUTOPLACE_NONE
Definition sch_item.h:74
void MoveSchematicItem(EDA_ITEM *aItem, const VECTOR2I &aDelta)
Move a schematic item by a delta.
void AlignSchematicItemsToGrid(SCH_SCREEN *aScreen, const std::vector< EDA_ITEM * > &aItems, EE_GRID_HELPER &aGrid, GRID_HELPER_GRIDS aSelectionGrid, const SCH_ALIGNMENT_CALLBACKS &aCallbacks)
Align a set of schematic items to the grid.
std::vector< EDA_ITEM * > EDA_ITEMS
#define QUIET_MODE
static bool isGraphicItemForDrop(const SCH_ITEM *aItem)
static long long sheetBorderArc(const SCH_SHEET *aSheet, SHEET_SIDE aSide, const VECTOR2I &aPos)
static void sheetBorderPos(const SCH_SHEET *aSheet, long long aArc, VECTOR2I &aPos, SHEET_SIDE &aSide)
static void cloneWireConnection(SCH_LINE *aNewLine, SCH_ITEM *aSource, SCH_EDIT_FRAME *aFrame)
SHEET_SIDE
Define the edge of the sheet that the sheet pin is positioned.
T * GetAppSettings(const char *aFilename)
Increment event parameters.
Definition actions.h:328
The EE_TYPE struct provides a type-specific auto-range iterator to the RTree.
Definition sch_rtree.h:198
A selection of information about a point in the schematic that might be eligible for turning into a j...
VECTOR2D m_lastKeyboardCursorPosition
Position of the above event.
bool m_lastKeyboardCursorPositionValid
Is last cursor motion event coming from keyboard arrow cursor motion action.
long m_lastKeyboardCursorCommand
ACTIONS::CURSOR_UP, ACTIONS::CURSOR_DOWN, etc.
Callbacks for alignment operations.
std::function< void(SCH_ITEM *aItem, const VECTOR2I &aPoint, EDA_ITEMS &aList)> m_getConnectedDragItems
Callback to get items connected to a given item at a specific point.
std::function< void(EDA_ITEM *aItem, const VECTOR2I &aDelta)> m_doMoveItem
Callback to move an item by a delta.
std::function< void(EDA_ITEM *aItem)> m_updateItem
Optional callback to update an item's display after modification.
Junctions that were hidden during the move.
bool moved
KIBIS top(path, &reporter)
KIBIS_PIN * pin
VECTOR2I end
int delta
@ TA_CHOICE_MENU_CHOICE
Context menu choice.
Definition tool_event.h:94
@ STS_CANCELLED
Definition tool_event.h:160
@ STS_FINISHED
Definition tool_event.h:159
@ STS_RUNNING
Definition tool_event.h:158
@ MD_CTRL
Definition tool_event.h:140
@ MD_SHIFT
Definition tool_event.h:139
@ TC_COMMAND
Definition tool_event.h:53
@ TC_MOUSE
Definition tool_event.h:51
@ TC_KEYBOARD
Definition tool_event.h:52
@ BUT_LEFT
Definition tool_event.h:128
@ BUT_RIGHT
Definition tool_event.h:129
wxLogTrace helper definitions.
KICAD_T
The set of class identification values stored in EDA_ITEM::m_structType.
Definition typeinfo.h:70
@ SCH_LINE_T
Definition typeinfo.h:159
@ SCH_NO_CONNECT_T
Definition typeinfo.h:156
@ SCH_SYMBOL_T
Definition typeinfo.h:168
@ SCH_FIELD_T
Definition typeinfo.h:146
@ SCH_DIRECTIVE_LABEL_T
Definition typeinfo.h:167
@ SCH_LABEL_T
Definition typeinfo.h:163
@ SCH_SHEET_T
Definition typeinfo.h:171
@ SCH_SHAPE_T
Definition typeinfo.h:145
@ SCH_HIER_LABEL_T
Definition typeinfo.h:165
@ SCH_BUS_BUS_ENTRY_T
Definition typeinfo.h:158
@ SCH_SHEET_PIN_T
Definition typeinfo.h:170
@ SCH_TEXT_T
Definition typeinfo.h:147
@ SCH_BUS_WIRE_ENTRY_T
Definition typeinfo.h:157
@ SCH_BITMAP_T
Definition typeinfo.h:160
@ SCH_TEXTBOX_T
Definition typeinfo.h:148
@ SCH_GLOBAL_LABEL_T
Definition typeinfo.h:164
@ SCH_JUNCTION_T
Definition typeinfo.h:155
@ SCH_PIN_T
Definition typeinfo.h:149
constexpr int sign(T val)
Definition util.h:166
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708
VECTOR2< double > VECTOR2D
Definition vector2d.h:707