KiCad PCB EDA Suite
Loading...
Searching...
No Matches
sch_align_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 The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 3
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, you may find one here:
18 * http://www.gnu.org/licenses/gpl-3.0.html,
19 * or you may search the http://www.gnu.org website for the version 3 license,
20 * or you may write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 */
23
24#include <algorithm>
25
26#include <bitmaps.h>
27#include <sch_actions.h>
28#include <sch_collectors.h>
29#include <sch_commit.h>
30#include <sch_edit_frame.h>
31#include <sch_item.h>
32#include <sch_selection.h>
33#include <sch_selection_tool.h>
36#include <tool/tool_event.h>
39#include <view/view_controls.h>
41#include <schematic.h>
42
44 SCH_TOOL_BASE<SCH_EDIT_FRAME>( "eeschema.Align" ),
45 m_alignMenu( nullptr )
46{
47}
48
49
54
55
57{
59
60 if( !m_alignMenu )
61 {
62 m_alignMenu = new CONDITIONAL_MENU( this );
64 m_alignMenu->SetUntranslatedTitle( _HKI( "Align" ) );
65
66 const auto canAlign = SELECTION_CONDITIONS::MoreThan( 1 );
67
68 m_alignMenu->AddItem( SCH_ACTIONS::alignLeft, canAlign );
69 m_alignMenu->AddItem( SCH_ACTIONS::alignCenterX, canAlign );
70 m_alignMenu->AddItem( SCH_ACTIONS::alignRight, canAlign );
71
72 m_alignMenu->AddSeparator( canAlign );
73 m_alignMenu->AddItem( SCH_ACTIONS::alignTop, canAlign );
74 m_alignMenu->AddItem( SCH_ACTIONS::alignCenterY, canAlign );
75 m_alignMenu->AddItem( SCH_ACTIONS::alignBottom, canAlign );
76 }
77
78 CONDITIONAL_MENU& selToolMenu = m_selectionTool->GetToolMenu().GetMenu();
79 selToolMenu.AddMenu( m_alignMenu, SELECTION_CONDITIONS::MoreThan( 1 ), 100 );
80
82
83 return true;
84}
85
86
87template< typename T >
88int SCH_ALIGN_TOOL::selectTarget( const std::vector<ITEM_BOX>& aItems,
89 const std::vector<ITEM_BOX>& aLocked, T aGetValue )
90{
92
93 if( !aLocked.empty() )
94 {
95 for( const ITEM_BOX& item : aLocked )
96 {
97 if( item.second.Contains( cursorPos ) )
98 return aGetValue( item );
99 }
100
101 return aGetValue( aLocked.front() );
102 }
103
104 for( const ITEM_BOX& item : aItems )
105 {
106 if( item.second.Contains( cursorPos ) )
107 return aGetValue( item );
108 }
109
110 return aGetValue( aItems.front() );
111}
112
113
114template< typename T >
115size_t SCH_ALIGN_TOOL::GetSelections( std::vector<ITEM_BOX>& aItemsToAlign,
116 std::vector<ITEM_BOX>& aLockedItems, T aCompare )
117{
118 SCH_SELECTION& selection = m_selectionTool->RequestSelection( SCH_COLLECTOR::MovableItems );
119
120 for( EDA_ITEM* item : selection )
121 {
122 if( !item->IsSCH_ITEM() )
123 continue;
124
125 SCH_ITEM* schItem = static_cast<SCH_ITEM*>( item );
126
127 if( schItem->GetParent() && schItem->GetParent()->IsSelected() )
128 continue;
129
130 BOX2I bbox = schItem->GetBoundingBox();
131
132 if( schItem->IsLocked() )
133 aLockedItems.emplace_back( schItem, bbox );
134 else
135 aItemsToAlign.emplace_back( schItem, bbox );
136 }
137
138 std::sort( aItemsToAlign.begin(), aItemsToAlign.end(), aCompare );
139 std::sort( aLockedItems.begin(), aLockedItems.end(), aCompare );
140
141 return aItemsToAlign.size();
142}
143
144
145void SCH_ALIGN_TOOL::moveItem( SCH_ITEM* aItem, const VECTOR2I& aDelta, SCH_COMMIT& aCommit )
146{
147 if( aDelta == VECTOR2I( 0, 0 ) )
148 return;
149
150 VECTOR2I delta = adjustDeltaForGrid( aItem, aDelta );
151
152 if( delta == VECTOR2I( 0, 0 ) )
153 return;
154
155 aCommit.Modify( aItem, m_frame->GetScreen(), RECURSE_MODE::RECURSE );
156 aItem->Move( delta );
157 aItem->ClearFlags( IS_MOVING );
158 updateItem( aItem, true );
159}
160
161
163{
164 if( aDelta == VECTOR2I( 0, 0 ) )
165 return aDelta;
166
168 GRID_HELPER_GRIDS gridType = grid.GetItemGrid( aItem );
169
170 if( gridType != GRID_CONNECTABLE )
171 return aDelta;
172
173 VECTOR2I desiredPos = aItem->GetPosition() + aDelta;
174 VECTOR2I snappedPos = grid.AlignGrid( desiredPos, gridType );
175
176 return snappedPos - aItem->GetPosition();
177}
178
179
189
190
192{
193 std::vector<ITEM_BOX> itemsToAlign;
194 std::vector<ITEM_BOX> lockedItems;
195
196 if( !GetSelections( itemsToAlign, lockedItems,
197 []( const ITEM_BOX& lhs, const ITEM_BOX& rhs )
198 {
199 return lhs.second.GetTop() < rhs.second.GetTop();
200 } ) )
201 {
202 return 0;
203 }
204
205 SCH_COMMIT commit( m_toolMgr );
206
207 int targetTop = selectTarget( itemsToAlign, lockedItems,
208 []( const ITEM_BOX& item )
209 {
210 return item.second.GetTop();
211 } );
212
213 for( const ITEM_BOX& item : itemsToAlign )
214 {
215 int difference = targetTop - item.second.GetTop();
216 moveItem( item.first, VECTOR2I( 0, difference ), commit );
217 }
218
219 doAlignCleanup( commit, itemsToAlign );
220
221 commit.Push( _( "Align to Top" ) );
222 return 0;
223}
224
225
227{
228 std::vector<ITEM_BOX> itemsToAlign;
229 std::vector<ITEM_BOX> lockedItems;
230
231 if( !GetSelections( itemsToAlign, lockedItems,
232 []( const ITEM_BOX& lhs, const ITEM_BOX& rhs )
233 {
234 return lhs.second.GetBottom() > rhs.second.GetBottom();
235 } ) )
236 {
237 return 0;
238 }
239
240 SCH_COMMIT commit( m_toolMgr );
241
242 int targetBottom = selectTarget( itemsToAlign, lockedItems,
243 []( const ITEM_BOX& item )
244 {
245 return item.second.GetBottom();
246 } );
247
248 for( const ITEM_BOX& item : itemsToAlign )
249 {
250 int difference = targetBottom - item.second.GetBottom();
251 moveItem( item.first, VECTOR2I( 0, difference ), commit );
252 }
253
254 doAlignCleanup( commit, itemsToAlign );
255
256 commit.Push( _( "Align to Bottom" ) );
257 return 0;
258}
259
260
262{
263 std::vector<ITEM_BOX> itemsToAlign;
264 std::vector<ITEM_BOX> lockedItems;
265
266 if( !GetSelections( itemsToAlign, lockedItems,
267 []( const ITEM_BOX& lhs, const ITEM_BOX& rhs )
268 {
269 return lhs.second.GetLeft() < rhs.second.GetLeft();
270 } ) )
271 {
272 return 0;
273 }
274
275 SCH_COMMIT commit( m_toolMgr );
276
277 int targetLeft = selectTarget( itemsToAlign, lockedItems,
278 []( const ITEM_BOX& item )
279 {
280 return item.second.GetLeft();
281 } );
282
283 for( const ITEM_BOX& item : itemsToAlign )
284 {
285 int difference = targetLeft - item.second.GetLeft();
286 moveItem( item.first, VECTOR2I( difference, 0 ), commit );
287 }
288
289 doAlignCleanup( commit, itemsToAlign );
290
291 commit.Push( _( "Align to Left" ) );
292 return 0;
293}
294
295
297{
298 std::vector<ITEM_BOX> itemsToAlign;
299 std::vector<ITEM_BOX> lockedItems;
300
301 if( !GetSelections( itemsToAlign, lockedItems,
302 []( const ITEM_BOX& lhs, const ITEM_BOX& rhs )
303 {
304 return lhs.second.GetRight() > rhs.second.GetRight();
305 } ) )
306 {
307 return 0;
308 }
309
310 SCH_COMMIT commit( m_toolMgr );
311
312 int targetRight = selectTarget( itemsToAlign, lockedItems,
313 []( const ITEM_BOX& item )
314 {
315 return item.second.GetRight();
316 } );
317
318 for( const ITEM_BOX& item : itemsToAlign )
319 {
320 int difference = targetRight - item.second.GetRight();
321 moveItem( item.first, VECTOR2I( difference, 0 ), commit );
322 }
323
324 doAlignCleanup( commit, itemsToAlign );
325
326 commit.Push( _( "Align to Right" ) );
327 return 0;
328}
329
330
332{
333 std::vector<ITEM_BOX> itemsToAlign;
334 std::vector<ITEM_BOX> lockedItems;
335
336 if( !GetSelections( itemsToAlign, lockedItems,
337 []( const ITEM_BOX& lhs, const ITEM_BOX& rhs )
338 {
339 return lhs.second.Centre().x < rhs.second.Centre().x;
340 } ) )
341 {
342 return 0;
343 }
344
345 SCH_COMMIT commit( m_toolMgr );
346
347 int targetX = selectTarget( itemsToAlign, lockedItems,
348 []( const ITEM_BOX& item )
349 {
350 return item.second.Centre().x;
351 } );
352
353 for( const ITEM_BOX& item : itemsToAlign )
354 {
355 int difference = targetX - item.second.Centre().x;
356 moveItem( item.first, VECTOR2I( difference, 0 ), commit );
357 }
358
359 doAlignCleanup( commit, itemsToAlign );
360
361 commit.Push( _( "Align to Middle" ) );
362 return 0;
363}
364
365
367{
368 std::vector<ITEM_BOX> itemsToAlign;
369 std::vector<ITEM_BOX> lockedItems;
370
371 if( !GetSelections( itemsToAlign, lockedItems,
372 []( const ITEM_BOX& lhs, const ITEM_BOX& rhs )
373 {
374 return lhs.second.Centre().y < rhs.second.Centre().y;
375 } ) )
376 {
377 return 0;
378 }
379
380 SCH_COMMIT commit( m_toolMgr );
381
382 int targetY = selectTarget( itemsToAlign, lockedItems,
383 []( const ITEM_BOX& item )
384 {
385 return item.second.Centre().y;
386 } );
387
388 for( const ITEM_BOX& item : itemsToAlign )
389 {
390 int difference = targetY - item.second.Centre().y;
391 moveItem( item.first, VECTOR2I( 0, difference ), commit );
392 }
393
394 doAlignCleanup( commit, itemsToAlign );
395
396 commit.Push( _( "Align to Center" ) );
397 return 0;
398}
399
400
401void SCH_ALIGN_TOOL::doAlignCleanup( SCH_COMMIT& aCommit, std::vector<ITEM_BOX>& aItems )
402{
404
405 SCH_SELECTION alignedItems;
406
407 for( const ITEM_BOX& item : aItems )
408 alignedItems.Add( item.first );
409
410 lwbTool->TrimOverLappingWires( &aCommit, &alignedItems );
411 lwbTool->AddJunctionsIfNeeded( &aCommit, &alignedItems );
412
413 for( EDA_ITEM* item : m_frame->GetScreen()->Items() )
414 item->ClearTempFlags();
415
416 m_frame->Schematic().CleanUp( &aCommit );
417
418 for( EDA_ITEM* item : m_frame->GetScreen()->Items() )
419 item->ClearEditFlags();
420}
BOX2< VECTOR2I > BOX2I
Definition box2.h:922
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:106
void AddMenu(ACTION_MENU *aMenu, const SELECTION_CONDITION &aCondition=SELECTION_CONDITIONS::ShowAlways, int aOrder=ANY_ORDER)
Add a submenu to the menu.
A base class for most all the KiCad significant classes used in schematics and boards.
Definition eda_item.h:99
virtual VECTOR2I GetPosition() const
Definition eda_item.h:278
virtual const BOX2I GetBoundingBox() const
Return the orthogonal bounding box of this object for display purposes.
Definition eda_item.cpp:120
void ClearFlags(EDA_ITEM_FLAGS aMask=EDA_ITEM_ALL_FLAGS)
Definition eda_item.h:150
bool IsSelected() const
Definition eda_item.h:128
EDA_ITEM * GetParent() const
Definition eda_item.h:113
virtual bool IsLocked() const
Definition eda_item.h:121
VECTOR2D GetCursorPosition() const
Return the current cursor position in world coordinates.
static TOOL_ACTION alignTop
static TOOL_ACTION alignRight
static TOOL_ACTION alignBottom
static TOOL_ACTION alignLeft
static TOOL_ACTION alignCenterX
static TOOL_ACTION alignCenterY
void doAlignCleanup(SCH_COMMIT &aCommit, std::vector< ITEM_BOX > &aItems)
int AlignTop(const TOOL_EVENT &aEvent)
int AlignLeft(const TOOL_EVENT &aEvent)
int selectTarget(const std::vector< ITEM_BOX > &aItems, const std::vector< ITEM_BOX > &aLocked, T aGetValue)
~SCH_ALIGN_TOOL() override
size_t GetSelections(std::vector< ITEM_BOX > &aItemsToAlign, std::vector< ITEM_BOX > &aLockedItems, T aCompare)
bool Init() override
Init() is called once upon a registration of the tool.
int AlignBottom(const TOOL_EVENT &aEvent)
VECTOR2I adjustDeltaForGrid(SCH_ITEM *aItem, const VECTOR2I &aDelta)
CONDITIONAL_MENU * m_alignMenu
int AlignRight(const TOOL_EVENT &aEvent)
std::pair< SCH_ITEM *, BOX2I > ITEM_BOX
void setTransitions() override
This method is meant to be overridden in order to specify handlers for events.
int AlignCenterY(const TOOL_EVENT &aEvent)
int AlignCenterX(const TOOL_EVENT &aEvent)
void moveItem(SCH_ITEM *aItem, const VECTOR2I &aDelta, SCH_COMMIT &aCommit)
static const std::vector< KICAD_T > MovableItems
virtual void Push(const wxString &aMessage=wxT("A commit"), int aCommitFlags=0) override
Execute the changes.
Schematic editor (Eeschema) main window.
Base class for any item which can be embedded within the SCHEMATIC container class,...
Definition sch_item.h:168
virtual void Move(const VECTOR2I &aMoveVector)
Move the item by aMoveVector to a new position.
Definition sch_item.h:399
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.
int TrimOverLappingWires(SCH_COMMIT *aCommit, SCH_SELECTION *aSelection)
Logic to remove wires when overlapping correct items.
void updateItem(EDA_ITEM *aItem, bool aUpdateRTree) const
bool Init() override
Init() is called once upon a registration of the tool.
SCH_TOOL_BASE(const std::string &aName)
SCH_SELECTION_TOOL * m_selectionTool
static SELECTION_CONDITION MoreThan(int aNumber)
Create a functor that tests if the number of selected items is greater than the value given as parame...
virtual void Add(EDA_ITEM *aItem)
Definition selection.cpp:42
KIGFX::VIEW_CONTROLS * getViewControls() const
Definition tool_base.cpp:44
Generic, UI-independent tool event.
Definition tool_event.h:171
void Go(int(SCH_EDIT_FRAME::*aStateFunc)(const TOOL_EVENT &), const TOOL_EVENT_LIST &aConditions=TOOL_EVENT(TC_ANY, TA_ANY))
#define _(s)
@ RECURSE
Definition eda_item.h:52
#define IS_MOVING
Item being moved.
GRID_HELPER_GRIDS
Definition grid_helper.h:44
@ GRID_CONNECTABLE
Definition grid_helper.h:48
#define _HKI(x)
Definition page_info.cpp:44
int delta
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:695