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