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, see <https://www.gnu.org/licenses/>.
18 */
19
20#include <algorithm>
21
22#include <bitmaps.h>
23#include <sch_actions.h>
24#include <sch_collectors.h>
25#include <sch_commit.h>
26#include <sch_edit_frame.h>
27#include <sch_item.h>
28#include <sch_selection.h>
29#include <sch_selection_tool.h>
32#include <tool/tool_event.h>
35#include <view/view_controls.h>
37#include <schematic.h>
38
40 SCH_TOOL_BASE<SCH_EDIT_FRAME>( "eeschema.Align" ),
41 m_alignMenu( nullptr )
42{
43}
44
45
50
51
53{
55
56 if( !m_alignMenu )
57 {
58 m_alignMenu = new CONDITIONAL_MENU( this );
60 m_alignMenu->SetUntranslatedTitle( _HKI( "Align" ) );
61
62 const auto canAlign = SELECTION_CONDITIONS::MoreThan( 1 );
63
64 m_alignMenu->AddItem( SCH_ACTIONS::alignLeft, canAlign );
65 m_alignMenu->AddItem( SCH_ACTIONS::alignCenterX, canAlign );
66 m_alignMenu->AddItem( SCH_ACTIONS::alignRight, canAlign );
67
68 m_alignMenu->AddSeparator( canAlign );
69 m_alignMenu->AddItem( SCH_ACTIONS::alignTop, canAlign );
70 m_alignMenu->AddItem( SCH_ACTIONS::alignCenterY, canAlign );
71 m_alignMenu->AddItem( SCH_ACTIONS::alignBottom, canAlign );
72 }
73
74 CONDITIONAL_MENU& selToolMenu = m_selectionTool->GetToolMenu().GetMenu();
75 selToolMenu.AddMenu( m_alignMenu, SELECTION_CONDITIONS::MoreThan( 1 ), 100 );
76
78
79 return true;
80}
81
82
83template< typename T >
84int SCH_ALIGN_TOOL::selectTarget( const std::vector<ITEM_BOX>& aItems,
85 const std::vector<ITEM_BOX>& aLocked, T aGetValue )
86{
88
89 if( !aLocked.empty() )
90 {
91 for( const ITEM_BOX& item : aLocked )
92 {
93 if( item.second.Contains( cursorPos ) )
94 return aGetValue( item );
95 }
96
97 return aGetValue( aLocked.front() );
98 }
99
100 for( const ITEM_BOX& item : aItems )
101 {
102 if( item.second.Contains( cursorPos ) )
103 return aGetValue( item );
104 }
105
106 return aGetValue( aItems.front() );
107}
108
109
110template< typename T >
111size_t SCH_ALIGN_TOOL::GetSelections( std::vector<ITEM_BOX>& aItemsToAlign,
112 std::vector<ITEM_BOX>& aLockedItems, T aCompare )
113{
114 SCH_SELECTION& selection = m_selectionTool->RequestSelection( SCH_COLLECTOR::MovableItems );
115
116 for( EDA_ITEM* item : selection )
117 {
118 if( !item->IsSCH_ITEM() )
119 continue;
120
121 SCH_ITEM* schItem = static_cast<SCH_ITEM*>( item );
122
123 if( schItem->GetParent() && schItem->GetParent()->IsSelected() )
124 continue;
125
126 BOX2I bbox = schItem->GetBoundingBox();
127
128 if( schItem->IsLocked() )
129 aLockedItems.emplace_back( schItem, bbox );
130 else
131 aItemsToAlign.emplace_back( schItem, bbox );
132 }
133
134 std::sort( aItemsToAlign.begin(), aItemsToAlign.end(), aCompare );
135 std::sort( aLockedItems.begin(), aLockedItems.end(), aCompare );
136
137 return aItemsToAlign.size();
138}
139
140
141void SCH_ALIGN_TOOL::moveItem( SCH_ITEM* aItem, const VECTOR2I& aDelta, SCH_COMMIT& aCommit )
142{
143 if( aDelta == VECTOR2I( 0, 0 ) )
144 return;
145
146 VECTOR2I delta = adjustDeltaForGrid( aItem, aDelta );
147
148 if( delta == VECTOR2I( 0, 0 ) )
149 return;
150
151 aCommit.Modify( aItem, m_frame->GetScreen(), RECURSE_MODE::RECURSE );
152 aItem->Move( delta );
153 aItem->ClearFlags( IS_MOVING );
154 updateItem( aItem, true );
155}
156
157
159{
160 if( aDelta == VECTOR2I( 0, 0 ) )
161 return aDelta;
162
164 GRID_HELPER_GRIDS gridType = grid.GetItemGrid( aItem );
165
166 if( gridType != GRID_CONNECTABLE )
167 return aDelta;
168
169 VECTOR2I desiredPos = aItem->GetPosition() + aDelta;
170 VECTOR2I snappedPos = grid.AlignGrid( desiredPos, gridType );
171
172 return snappedPos - aItem->GetPosition();
173}
174
175
185
186
188{
189 std::vector<ITEM_BOX> itemsToAlign;
190 std::vector<ITEM_BOX> lockedItems;
191
192 if( !GetSelections( itemsToAlign, lockedItems,
193 []( const ITEM_BOX& lhs, const ITEM_BOX& rhs )
194 {
195 return lhs.second.GetTop() < rhs.second.GetTop();
196 } ) )
197 {
198 return 0;
199 }
200
201 SCH_COMMIT commit( m_toolMgr );
202
203 int targetTop = selectTarget( itemsToAlign, lockedItems,
204 []( const ITEM_BOX& item )
205 {
206 return item.second.GetTop();
207 } );
208
209 for( const ITEM_BOX& item : itemsToAlign )
210 {
211 int difference = targetTop - item.second.GetTop();
212 moveItem( item.first, VECTOR2I( 0, difference ), commit );
213 }
214
215 doAlignCleanup( commit, itemsToAlign );
216
217 commit.Push( _( "Align to Top" ) );
218 return 0;
219}
220
221
223{
224 std::vector<ITEM_BOX> itemsToAlign;
225 std::vector<ITEM_BOX> lockedItems;
226
227 if( !GetSelections( itemsToAlign, lockedItems,
228 []( const ITEM_BOX& lhs, const ITEM_BOX& rhs )
229 {
230 return lhs.second.GetBottom() > rhs.second.GetBottom();
231 } ) )
232 {
233 return 0;
234 }
235
236 SCH_COMMIT commit( m_toolMgr );
237
238 int targetBottom = selectTarget( itemsToAlign, lockedItems,
239 []( const ITEM_BOX& item )
240 {
241 return item.second.GetBottom();
242 } );
243
244 for( const ITEM_BOX& item : itemsToAlign )
245 {
246 int difference = targetBottom - item.second.GetBottom();
247 moveItem( item.first, VECTOR2I( 0, difference ), commit );
248 }
249
250 doAlignCleanup( commit, itemsToAlign );
251
252 commit.Push( _( "Align to Bottom" ) );
253 return 0;
254}
255
256
258{
259 std::vector<ITEM_BOX> itemsToAlign;
260 std::vector<ITEM_BOX> lockedItems;
261
262 if( !GetSelections( itemsToAlign, lockedItems,
263 []( const ITEM_BOX& lhs, const ITEM_BOX& rhs )
264 {
265 return lhs.second.GetLeft() < rhs.second.GetLeft();
266 } ) )
267 {
268 return 0;
269 }
270
271 SCH_COMMIT commit( m_toolMgr );
272
273 int targetLeft = selectTarget( itemsToAlign, lockedItems,
274 []( const ITEM_BOX& item )
275 {
276 return item.second.GetLeft();
277 } );
278
279 for( const ITEM_BOX& item : itemsToAlign )
280 {
281 int difference = targetLeft - item.second.GetLeft();
282 moveItem( item.first, VECTOR2I( difference, 0 ), commit );
283 }
284
285 doAlignCleanup( commit, itemsToAlign );
286
287 commit.Push( _( "Align to Left" ) );
288 return 0;
289}
290
291
293{
294 std::vector<ITEM_BOX> itemsToAlign;
295 std::vector<ITEM_BOX> lockedItems;
296
297 if( !GetSelections( itemsToAlign, lockedItems,
298 []( const ITEM_BOX& lhs, const ITEM_BOX& rhs )
299 {
300 return lhs.second.GetRight() > rhs.second.GetRight();
301 } ) )
302 {
303 return 0;
304 }
305
306 SCH_COMMIT commit( m_toolMgr );
307
308 int targetRight = selectTarget( itemsToAlign, lockedItems,
309 []( const ITEM_BOX& item )
310 {
311 return item.second.GetRight();
312 } );
313
314 for( const ITEM_BOX& item : itemsToAlign )
315 {
316 int difference = targetRight - item.second.GetRight();
317 moveItem( item.first, VECTOR2I( difference, 0 ), commit );
318 }
319
320 doAlignCleanup( commit, itemsToAlign );
321
322 commit.Push( _( "Align to Right" ) );
323 return 0;
324}
325
326
328{
329 std::vector<ITEM_BOX> itemsToAlign;
330 std::vector<ITEM_BOX> lockedItems;
331
332 if( !GetSelections( itemsToAlign, lockedItems,
333 []( const ITEM_BOX& lhs, const ITEM_BOX& rhs )
334 {
335 return lhs.second.Centre().x < rhs.second.Centre().x;
336 } ) )
337 {
338 return 0;
339 }
340
341 SCH_COMMIT commit( m_toolMgr );
342
343 int targetX = selectTarget( itemsToAlign, lockedItems,
344 []( const ITEM_BOX& item )
345 {
346 return item.second.Centre().x;
347 } );
348
349 for( const ITEM_BOX& item : itemsToAlign )
350 {
351 int difference = targetX - item.second.Centre().x;
352 moveItem( item.first, VECTOR2I( difference, 0 ), commit );
353 }
354
355 doAlignCleanup( commit, itemsToAlign );
356
357 commit.Push( _( "Align to Middle" ) );
358 return 0;
359}
360
361
363{
364 std::vector<ITEM_BOX> itemsToAlign;
365 std::vector<ITEM_BOX> lockedItems;
366
367 if( !GetSelections( itemsToAlign, lockedItems,
368 []( const ITEM_BOX& lhs, const ITEM_BOX& rhs )
369 {
370 return lhs.second.Centre().y < rhs.second.Centre().y;
371 } ) )
372 {
373 return 0;
374 }
375
376 SCH_COMMIT commit( m_toolMgr );
377
378 int targetY = selectTarget( itemsToAlign, lockedItems,
379 []( const ITEM_BOX& item )
380 {
381 return item.second.Centre().y;
382 } );
383
384 for( const ITEM_BOX& item : itemsToAlign )
385 {
386 int difference = targetY - item.second.Centre().y;
387 moveItem( item.first, VECTOR2I( 0, difference ), commit );
388 }
389
390 doAlignCleanup( commit, itemsToAlign );
391
392 commit.Push( _( "Align to Center" ) );
393 return 0;
394}
395
396
397void SCH_ALIGN_TOOL::doAlignCleanup( SCH_COMMIT& aCommit, std::vector<ITEM_BOX>& aItems )
398{
400
401 SCH_SELECTION alignedItems;
402
403 for( const ITEM_BOX& item : aItems )
404 alignedItems.Add( item.first );
405
406 lwbTool->TrimOverLappingWires( &aCommit, &alignedItems );
407 lwbTool->AddJunctionsIfNeeded( &aCommit, &alignedItems );
408
409 for( EDA_ITEM* item : m_frame->GetScreen()->Items() )
410 item->ClearTempFlags();
411
412 m_frame->Schematic().CleanUp( &aCommit );
413
414 for( EDA_ITEM* item : m_frame->GetScreen()->Items() )
415 item->ClearEditFlags();
416}
BOX2< VECTOR2I > BOX2I
Definition box2.h:918
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 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:96
virtual VECTOR2I GetPosition() const
Definition eda_item.h:282
virtual const BOX2I GetBoundingBox() const
Return the orthogonal bounding box of this object for display purposes.
Definition eda_item.cpp:135
void ClearFlags(EDA_ITEM_FLAGS aMask=EDA_ITEM_ALL_FLAGS)
Definition eda_item.h:154
bool IsSelected() const
Definition eda_item.h:132
EDA_ITEM * GetParent() const
Definition eda_item.h:110
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:162
bool IsLocked() const override
Definition sch_item.cpp:148
virtual void Move(const VECTOR2I &aMoveVector)
Move the item by aMoveVector to a new position.
Definition sch_item.h:396
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:38
KIGFX::VIEW_CONTROLS * getViewControls() const
Definition tool_base.cpp:40
Generic, UI-independent tool event.
Definition tool_event.h:167
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:49
#define IS_MOVING
Item being moved.
GRID_HELPER_GRIDS
Definition grid_helper.h:40
@ GRID_CONNECTABLE
Definition grid_helper.h:44
#define _HKI(x)
Definition page_info.cpp:40
int delta
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683