KiCad PCB EDA Suite
Loading...
Searching...
No Matches
symbol_editor_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 <tool/tool_manager.h>
23#include <sch_actions.h>
24#include <ee_grid_helper.h>
25#include <eda_item.h>
27#include <sch_shape.h>
28#include <sch_group.h>
29#include <sch_commit.h>
30#include <wx/debug.h>
31#include <view/view_controls.h>
34
35
37 SCH_TOOL_BASE( "eeschema.SymbolMoveTool" ),
38 m_moveInProgress( false )
39{
40}
41
42
44{
46
47 //
48 // Add move actions to the selection tool menu
49 //
50 CONDITIONAL_MENU& selToolMenu = m_selectionTool->GetToolMenu().GetMenu();
51
52 auto canMove =
53 [&]( const SELECTION& sel )
54 {
56 wxCHECK( editor, false );
57
58 if( !editor->IsSymbolEditable() )
59 return false;
60
61 if( editor->IsSymbolAlias() )
62 {
63 for( EDA_ITEM* item : sel )
64 {
65 if( item->Type() != SCH_FIELD_T )
66 return false;
67 }
68 }
69
70 return true;
71 };
72
73 selToolMenu.AddItem( SCH_ACTIONS::move, canMove && SCH_CONDITIONS::IdleSelection, 150 );
75
76 return true;
77}
78
79
81{
82 SCH_TOOL_BASE::Reset( aReason );
83
84 if( aReason == MODEL_RELOAD )
85 m_moveInProgress = false;
86}
87
88
90{
91 if( SCH_COMMIT* commit = dynamic_cast<SCH_COMMIT*>( aEvent.Commit() ) )
92 {
93 wxCHECK( aEvent.SynchronousState(), 0 );
94 aEvent.SynchronousState()->store( STS_RUNNING );
95
96 if( doMoveSelection( aEvent, commit ) )
97 aEvent.SynchronousState()->store( STS_FINISHED );
98 else
99 aEvent.SynchronousState()->store( STS_CANCELLED );
100 }
101 else
102 {
103 SCH_COMMIT localCommit( m_toolMgr );
104
105 if( doMoveSelection( aEvent, &localCommit ) )
106 localCommit.Push( _( "Move" ) );
107 else
108 localCommit.Revert();
109 }
110
111 return 0;
112}
113
114
116{
119
120 m_anchorPos = { 0, 0 };
121
122 // Be sure that there is at least one item that we can move. If there's no selection try
123 // looking for the stuff under mouse cursor (i.e. Kicad old-style hover selection).
124 SCH_SELECTION& selection = m_frame->IsSymbolAlias() ? m_selectionTool->RequestSelection( { SCH_FIELD_T } )
125 : m_selectionTool->RequestSelection();
126 bool unselect = selection.IsHover();
127
128 if( !m_frame->IsSymbolEditable() || selection.Empty() )
129 return false;
130
131 if( m_moveInProgress )
132 {
133 // The tool hotkey is interpreted as a click when already moving
134 m_toolMgr->RunAction( ACTIONS::cursorClick );
135 return true;
136 }
137
138 m_frame->PushTool( aEvent );
139
140 Activate();
141 // Must be done after Activate() so that it gets set into the correct context
142 controls->ShowCursor( true );
143 controls->SetAutoPan( true );
144
145 bool restore_state = false;
146 TOOL_EVENT copy = aEvent;
147 TOOL_EVENT* evt = &copy;
148 VECTOR2I prevPos;
149 VECTOR2I moveOffset;
150
151 // Axis locking for arrow key movement
152 enum class AXIS_LOCK { NONE, HORIZONTAL, VERTICAL };
153 AXIS_LOCK axisLock = AXIS_LOCK::NONE;
154 long lastArrowKeyAction = 0;
155
156 aCommit->Modify( m_frame->GetCurSymbol(), m_frame->GetScreen() );
157
158 m_cursor = controls->GetCursorPosition( !aEvent.DisableGridSnapping() );
159
160 // Main loop: keep receiving events
161 do
162 {
163 m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::MOVING );
164 grid.SetSnap( !evt->Modifier( MD_SHIFT ) );
165 grid.SetUseGrid( getView()->GetGAL()->GetGridSnapping() && !evt->DisableGridSnapping() );
166
167 if( evt->IsAction( &SCH_ACTIONS::move )
168 || evt->IsMotion()
169 || evt->IsDrag( BUT_LEFT )
171 {
172 GRID_HELPER_GRIDS snapLayer = grid.GetSelectionGrid( selection );
173
174 if( !m_moveInProgress ) // Prepare to start moving/dragging
175 {
176 SCH_ITEM* lib_item = static_cast<SCH_ITEM*>( selection.Front() );
177
178 // Pick up any synchronized pins
179 //
180 // Careful when pasting. The pasted pin will be at the same location as it
181 // was copied from, leading us to believe it's a synchronized pin. It's not.
182 if( m_frame->SynchronizePins() && !( lib_item->GetEditFlags() & IS_PASTED ) )
183 {
184 std::set<SCH_PIN*> sync_pins;
185
186 for( EDA_ITEM* sel_item : selection )
187 {
188 lib_item = static_cast<SCH_ITEM*>( sel_item );
189
190 if( lib_item->Type() == SCH_PIN_T )
191 {
192 SCH_PIN* cur_pin = static_cast<SCH_PIN*>( lib_item );
193 LIB_SYMBOL* symbol = m_frame->GetCurSymbol();
194 std::vector<bool> got_unit( symbol->GetUnitCount() + 1 );
195
196 got_unit[cur_pin->GetUnit()] = true;
197
198 for( SCH_PIN* pin : symbol->GetPins() )
199 {
200 if( !got_unit[pin->GetUnit()]
201 && pin->GetPosition() == cur_pin->GetPosition()
202 && pin->GetOrientation() == cur_pin->GetOrientation()
203 && pin->GetBodyStyle() == cur_pin->GetBodyStyle()
204 && pin->GetType() == cur_pin->GetType()
205 && pin->GetName() == cur_pin->GetName() )
206 {
207 if( sync_pins.insert( pin ).second )
208 got_unit[pin->GetUnit()] = true;
209 }
210 }
211 }
212 }
213
214 for( SCH_PIN* pin : sync_pins )
215 m_selectionTool->AddItemToSel( pin, true /*quiet mode*/ );
216 }
217
218 // Apply any initial offset in case we're coming from a previous command.
219 //
220 for( EDA_ITEM* item : selection )
221 moveItem( item, moveOffset );
222
223 // Set up the starting position and move/drag offset
224 //
225 m_cursor = controls->GetCursorPosition( !evt->DisableGridSnapping() );
226
227 if( lib_item->IsNew() )
228 {
229 m_anchorPos = selection.GetReferencePoint();
231
232 // Drag items to the current cursor position
233 for( EDA_ITEM* item : selection )
234 {
235 SCH_ITEM* schItem = static_cast<SCH_ITEM*>( item );
236
237 moveItem( schItem, delta );
238 updateItem( schItem, false );
239
240 // While SCH_COMMIT::Push() will add any new items to the entered group,
241 // we need to do it earlier so that the previews while moving are correct.
242 if( SCH_GROUP* enteredGroup = m_selectionTool->GetEnteredGroup() )
243 {
244 if( schItem->IsGroupableType() && !item->GetParentGroup() )
245 {
246 aCommit->Modify( enteredGroup, m_frame->GetScreen(), RECURSE_MODE::NO_RECURSE );
247 enteredGroup->AddItem( schItem );
248 }
249 }
250 }
251
253 }
254 else if( m_frame->GetMoveWarpsCursor() )
255 {
256 // User wants to warp the mouse
257 m_cursor = grid.BestDragOrigin( m_cursor, snapLayer, selection );
258 selection.SetReferencePoint( m_cursor );
260 }
261 else
262 {
263 m_cursor = controls->GetCursorPosition( !evt->DisableGridSnapping() );
265 }
266
267 controls->SetCursorPosition( m_cursor, false );
268
269 prevPos = m_cursor;
270 controls->SetAutoPan( true );
271 m_moveInProgress = true;
272 }
273
274 //------------------------------------------------------------------------
275 // Follow the mouse
276 //
277 // We need to bypass refreshPreview action here because it is triggered by the move,
278 // so we were getting double-key events that toggled the axis locking if you
279 // pressed them in a certain order.
281 {
282 VECTOR2I keyboardPos( controls->GetSettings().m_lastKeyboardCursorPosition );
283 long action = controls->GetSettings().m_lastKeyboardCursorCommand;
284
285 grid.SetSnap( false );
286 m_cursor = grid.Align( keyboardPos, snapLayer );
287
288 // Update axis lock based on arrow key press
289 if( action == ACTIONS::CURSOR_LEFT || action == ACTIONS::CURSOR_RIGHT )
290 {
291 if( axisLock == AXIS_LOCK::HORIZONTAL )
292 {
293 // Check if opposite horizontal key pressed to unlock
294 if( ( lastArrowKeyAction == ACTIONS::CURSOR_LEFT && action == ACTIONS::CURSOR_RIGHT ) ||
295 ( lastArrowKeyAction == ACTIONS::CURSOR_RIGHT && action == ACTIONS::CURSOR_LEFT ) )
296 {
297 axisLock = AXIS_LOCK::NONE;
298 }
299 // Same direction axis, keep locked
300 }
301 else
302 {
303 axisLock = AXIS_LOCK::HORIZONTAL;
304 }
305 }
306 else if( action == ACTIONS::CURSOR_UP || action == ACTIONS::CURSOR_DOWN )
307 {
308 if( axisLock == AXIS_LOCK::VERTICAL )
309 {
310 // Check if opposite vertical key pressed to unlock
311 if( ( lastArrowKeyAction == ACTIONS::CURSOR_UP && action == ACTIONS::CURSOR_DOWN ) ||
312 ( lastArrowKeyAction == ACTIONS::CURSOR_DOWN && action == ACTIONS::CURSOR_UP ) )
313 {
314 axisLock = AXIS_LOCK::NONE;
315 }
316 // Same direction axis, keep locked
317 }
318 else
319 {
320 axisLock = AXIS_LOCK::VERTICAL;
321 }
322 }
323
324 lastArrowKeyAction = action;
325 }
326 else
327 {
328 m_cursor = grid.BestSnapAnchor( controls->GetCursorPosition( false ), snapLayer,
329 selection );
330 }
331
332 if( axisLock == AXIS_LOCK::HORIZONTAL )
333 m_cursor.y = prevPos.y;
334 else if( axisLock == AXIS_LOCK::VERTICAL )
335 m_cursor.x = prevPos.x;
336
337 VECTOR2I delta( m_cursor - prevPos );
339
340 moveOffset += delta;
341 prevPos = m_cursor;
342
343 for( EDA_ITEM* item : selection )
344 {
345 moveItem( item, delta );
346 updateItem( item, false );
347 }
348
350 }
351 //------------------------------------------------------------------------
352 // Handle cancel
353 //
354 else if( evt->IsCancelInteractive() || evt->IsActivate() )
355 {
356 if( m_moveInProgress )
357 {
358 evt->SetPassEvent( false );
359 restore_state = true;
360 }
361
362 break;
363 }
364 //------------------------------------------------------------------------
365 // Handle TOOL_ACTION special cases
366 //
367 else if( evt->Action() == TA_UNDO_REDO_PRE )
368 {
369 unselect = true;
370 break;
371 }
372 else if( evt->IsAction( &ACTIONS::doDelete ) )
373 {
374 // Exit on a remove operation; there is no further processing for removed items.
375 break;
376 }
377 else if( evt->IsAction( &ACTIONS::duplicate ) )
378 {
379 wxBell();
380 }
381 //------------------------------------------------------------------------
382 // Handle context menu
383 //
384 else if( evt->IsClick( BUT_RIGHT ) )
385 {
386 m_menu->ShowContextMenu( m_selectionTool->GetSelection() );
387 }
388 //------------------------------------------------------------------------
389 // Handle drop
390 //
391 else if( evt->IsMouseUp( BUT_LEFT )
392 || evt->IsClick( BUT_LEFT )
393 || evt->IsDblClick( BUT_LEFT ) )
394 {
395 if( selection.GetSize() == 1 && selection.Front()->Type() == SCH_PIN_T )
396 {
398
399 try
400 {
401 SCH_PIN* curr_pin = static_cast<SCH_PIN*>( selection.Front() );
402
403 if( pinTool->PlacePin( aCommit, curr_pin ) )
404 {
405 // PlacePin() clears the current selection, which we don't want. Not only
406 // is it a poor user experience, but it also prevents us from doing the
407 // proper cleanup at the end of this routine (ie: clearing the edit flags).
408 m_selectionTool->AddItemToSel( curr_pin, true /*quiet mode*/ );
409 }
410 else
411 {
412 restore_state = true;
413 }
414 }
415 catch( const boost::bad_pointer& e )
416 {
417 restore_state = true;
418 wxFAIL_MSG( wxString::Format( wxT( "Boost pointer exception occurred: %s" ),
419 e.what() ) );
420 }
421 }
422
423 break; // Finish
424 }
425 else
426 {
427 evt->SetPassEvent();
428 }
429
430 } while( ( evt = Wait() ) ); // Assignment intentional; not equality test
431
432 controls->ForceCursorPosition( false );
433 controls->ShowCursor( false );
434 controls->SetAutoPan( false );
435
436 m_anchorPos = { 0, 0 };
437
438 for( EDA_ITEM* item : selection )
439 item->ClearEditFlags();
440
441 if( unselect )
443
444 m_moveInProgress = false;
445 m_frame->PopTool( aEvent );
446
447 return !restore_state;
448}
449
450
452{
454 SCH_SELECTION& selection = m_selectionTool->RequestSelection();
455 SCH_COMMIT commit( m_toolMgr );
456
457 for( EDA_ITEM* item : selection )
458 {
459 VECTOR2I newPos = grid.AlignGrid( item->GetPosition(), grid.GetItemGrid( item ) );
460 VECTOR2I delta = newPos - item->GetPosition();
461
462 if( delta != VECTOR2I( 0, 0 ) )
463 {
464 commit.Modify( item, m_frame->GetScreen(), RECURSE_MODE::RECURSE );
465 static_cast<SCH_ITEM*>( item )->Move( delta );
466 updateItem( item, true );
467 };
468
469 if( SCH_PIN* pin = dynamic_cast<SCH_PIN*>( item ) )
470 {
471 int length = pin->GetLength();
472 int pinGrid;
473
474 if( pin->GetOrientation() == PIN_ORIENTATION::PIN_LEFT
475 || pin->GetOrientation() == PIN_ORIENTATION::PIN_RIGHT )
476 {
477 pinGrid = KiROUND( grid.GetGridSize( grid.GetItemGrid( item ) ).x );
478 }
479 else
480 {
481 pinGrid = KiROUND( grid.GetGridSize( grid.GetItemGrid( item ) ).y );
482 }
483
484 int newLength = KiROUND( (double) length / pinGrid ) * pinGrid;
485
486 if( newLength > 0 )
487 pin->SetLength( newLength );
488 }
489 }
490
492
493 commit.Push( _( "Align Items to Grid" ) );
494 return 0;
495}
496
497
499{
500 static_cast<SCH_ITEM*>( aItem )->Move( aDelta );
501 aItem->SetFlags( IS_MOVING );
502}
503
504
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:986
@ CURSOR_RIGHT
Definition actions.h:307
@ CURSOR_LEFT
Definition actions.h:305
@ CURSOR_UP
Definition actions.h:301
@ CURSOR_DOWN
Definition actions.h:303
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 selectionClear
Clear the current selection.
Definition actions.h:220
static TOOL_ACTION refreshPreview
Definition actions.h:155
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
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.
A base class for most all the KiCad significant classes used in schematics and boards.
Definition eda_item.h:96
EDA_ITEM_FLAGS GetEditFlags() const
Definition eda_item.h:158
void SetFlags(EDA_ITEM_FLAGS aMask)
Definition eda_item.h:152
KICAD_T Type() const
Returns the type of object.
Definition eda_item.h:108
bool IsNew() const
Definition eda_item.h:129
static const TOOL_EVENT SelectedItemsMoved
Used to inform tools that the selection should temporarily be non-editable.
Definition actions.h:351
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.
Define a library symbol object.
Definition lib_symbol.h:79
std::vector< SCH_PIN * > GetPins() const override
int GetUnitCount() const override
static TOOL_ACTION alignToGrid
static TOOL_ACTION move
virtual void Push(const wxString &aMessage=wxT("A commit"), int aCommitFlags=0) override
Execute the changes.
virtual void Revert() override
Revert the commit by restoring the modified items state.
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:162
int GetBodyStyle() const
Definition sch_item.h:242
int GetUnit() const
Definition sch_item.h:233
bool IsGroupableType() const
Definition sch_item.cpp:113
const wxString & GetName() const
Definition sch_pin.cpp:494
PIN_ORIENTATION GetOrientation() const
Definition sch_pin.cpp:353
VECTOR2I GetPosition() const override
Definition sch_pin.cpp:345
ELECTRICAL_PINTYPE GetType() const
Definition sch_pin.cpp:402
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
static bool IdleSelection(const SELECTION &aSelection)
Test if all selected items are not being edited.
bool IsHover() const
Definition selection.h:85
virtual unsigned int GetSize() const override
Return the number of stored items.
Definition selection.h:101
EDA_ITEM * Front() const
Definition selection.h:173
bool Empty() const
Checks if there is anything selected.
Definition selection.h:111
bool doMoveSelection(const TOOL_EVENT &aEvent, SCH_COMMIT *aCommit)
void Reset(RESET_REASON aReason) override
Bring the tool to a known, initial state.
void setTransitions() override
This method is meant to be overridden in order to specify handlers for events.
void moveItem(EDA_ITEM *aItem, const VECTOR2I &aDelta)
Set up handlers for various events.
bool Init() override
Init() is called once upon a registration of the tool.
int Main(const TOOL_EVENT &aEvent)
Run an interactive move of the selected items, or the item under the cursor.
int AlignElements(const TOOL_EVENT &aEvent)
Align selected elements to the grid.
bool m_moveInProgress
Last cursor position (needed for getModificationPoint() to avoid changes of edit reference point).
bool PlacePin(SCH_COMMIT *aCommit, SCH_PIN *aPin)
The symbol library editor main window.
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 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
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.
bool IsDblClick(int aButtonMask=BUT_ANY) const
std::atomic< SYNCRONOUS_TOOL_STATE > * SynchronousState() const
Definition tool_event.h:276
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
void Go(int(SYMBOL_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))
@ MOVING
Definition cursors.h:44
#define _(s)
@ RECURSE
Definition eda_item.h:49
@ NO_RECURSE
Definition eda_item.h:50
#define IS_PASTED
Modifier on IS_NEW which indicates it came from clipboard.
#define IS_MOVING
Item being moved.
@ NONE
Definition eda_shape.h:72
GRID_HELPER_GRIDS
Definition grid_helper.h:40
@ PIN_RIGHT
The pin extends rightwards from the connection point.
Definition pin_type.h:107
@ PIN_LEFT
The pin extends leftwards from the connection point: Probably on the right side of the symbol.
Definition pin_type.h:114
Class to handle a set of SCH_ITEMs.
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.
KIBIS_PIN * pin
int delta
@ TA_UNDO_REDO_PRE
This event is sent before undo/redo command is performed.
Definition tool_event.h:102
@ STS_CANCELLED
Definition tool_event.h:160
@ STS_FINISHED
Definition tool_event.h:159
@ STS_RUNNING
Definition tool_event.h:158
@ MD_SHIFT
Definition tool_event.h:139
@ BUT_LEFT
Definition tool_event.h:128
@ BUT_RIGHT
Definition tool_event.h:129
@ SCH_FIELD_T
Definition typeinfo.h:147
@ SCH_PIN_T
Definition typeinfo.h:150
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683