1 /*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2014-2016 CERN
5 * Copyright (C) 2021 KiCad Developers, see AUTHORS.txt for contributors.
6 * @author Maciej Suminski <maciej.suminski@cern.ch>
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License
10 * as published by the Free Software Foundation; either version 2
11 * of the License, or (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, you may find one here:
20 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
21 * or you may search the http://www.gnu.org website for the version 2 license,
22 * or you may write to the Free Software Foundation, Inc.,
23 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
24 */
25 #include "tool/selection.h"
26 #include "placement_tool.h"
27 #include "pcb_actions.h"
28 #include "pcb_selection_tool.h"
29
30 #include <ratsnest/ratsnest_data.h>
31 #include <tool/tool_manager.h>
32
33 #include <pcb_edit_frame.h>
34 #include <board.h>
35 #include <board_commit.h>
36 #include <bitmaps.h>
37
38 #include <confirm.h>
39 #include <menus_helpers.h>
40
41
ALIGN_DISTRIBUTE_TOOL()42 ALIGN_DISTRIBUTE_TOOL::ALIGN_DISTRIBUTE_TOOL() :
43 TOOL_INTERACTIVE( "pcbnew.Placement" ),
44 m_selectionTool( nullptr ),
45 m_placementMenu( nullptr ),
46 m_frame( nullptr )
47 {
48 }
49
~ALIGN_DISTRIBUTE_TOOL()50 ALIGN_DISTRIBUTE_TOOL::~ALIGN_DISTRIBUTE_TOOL()
51 {
52 delete m_placementMenu;
53 }
54
55
Init()56 bool ALIGN_DISTRIBUTE_TOOL::Init()
57 {
58 // Find the selection tool, so they can cooperate
59 m_selectionTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
60 m_frame = getEditFrame<PCB_BASE_FRAME>();
61
62 // Create a context menu and make it available through selection tool
63 m_placementMenu = new ACTION_MENU( true, this );
64 m_placementMenu->SetIcon( BITMAPS::align_items );
65 m_placementMenu->SetTitle( _( "Align/Distribute" ) );
66
67 // Add all align/distribute commands
68 m_placementMenu->Add( PCB_ACTIONS::alignLeft );
69 m_placementMenu->Add( PCB_ACTIONS::alignCenterX );
70 m_placementMenu->Add( PCB_ACTIONS::alignRight );
71
72 m_placementMenu->AppendSeparator();
73 m_placementMenu->Add( PCB_ACTIONS::alignTop );
74 m_placementMenu->Add( PCB_ACTIONS::alignCenterY );
75 m_placementMenu->Add( PCB_ACTIONS::alignBottom );
76
77 m_placementMenu->AppendSeparator();
78 m_placementMenu->Add( PCB_ACTIONS::distributeHorizontally );
79 m_placementMenu->Add( PCB_ACTIONS::distributeVertically );
80
81 CONDITIONAL_MENU& selToolMenu = m_selectionTool->GetToolMenu().GetMenu();
82 selToolMenu.AddMenu( m_placementMenu, SELECTION_CONDITIONS::MoreThan( 1 ) );
83
84 return true;
85 }
86
87
88 template <class T>
GetBoundingBoxes(const T & aItems)89 ALIGNMENT_RECTS GetBoundingBoxes( const T& aItems )
90 {
91 ALIGNMENT_RECTS rects;
92
93 for( EDA_ITEM* item : aItems )
94 {
95 BOARD_ITEM* boardItem = static_cast<BOARD_ITEM*>( item );
96
97 if( item->Type() == PCB_FOOTPRINT_T )
98 {
99 FOOTPRINT* footprint = static_cast<FOOTPRINT*>( item );
100 rects.emplace_back( std::make_pair( boardItem,
101 footprint->GetBoundingBox( false, false ) ) );
102 }
103 else
104 {
105 rects.emplace_back( std::make_pair( boardItem, item->GetBoundingBox() ) );
106 }
107 }
108
109 return rects;
110 }
111
112
113 template< typename T >
selectTarget(ALIGNMENT_RECTS & aItems,ALIGNMENT_RECTS & aLocked,T aGetValue)114 int ALIGN_DISTRIBUTE_TOOL::selectTarget( ALIGNMENT_RECTS& aItems, ALIGNMENT_RECTS& aLocked,
115 T aGetValue )
116 {
117 wxPoint curPos = (wxPoint) getViewControls()->GetCursorPosition();
118
119 // Prefer locked items to unlocked items.
120 // Secondly, prefer items under the cursor to other items.
121
122 if( aLocked.size() >= 1 )
123 {
124 for( const ALIGNMENT_RECT& item : aLocked )
125 {
126 if( item.second.Contains( curPos ) )
127 return aGetValue( item );
128 }
129
130 return aGetValue( aLocked.front() );
131 }
132
133 for( const ALIGNMENT_RECT& item : aItems )
134 {
135 if( item.second.Contains( curPos ) )
136 return aGetValue( item );
137 }
138
139 return aGetValue( aItems.front() );
140 }
141
142
143 template< typename T >
GetSelections(ALIGNMENT_RECTS & aItemsToAlign,ALIGNMENT_RECTS & aLockedItems,T aCompare)144 size_t ALIGN_DISTRIBUTE_TOOL::GetSelections( ALIGNMENT_RECTS& aItemsToAlign,
145 ALIGNMENT_RECTS& aLockedItems, T aCompare )
146 {
147 PCB_SELECTION& selection = m_selectionTool->RequestSelection(
148 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
149 {
150 // Iterate from the back so we don't have to worry about removals.
151 for( int i = aCollector.GetCount() - 1; i >= 0; --i )
152 {
153 BOARD_ITEM* item = aCollector[i];
154
155 if( item->Type() == PCB_MARKER_T )
156 aCollector.Remove( item );
157 }
158 } );
159
160 std::vector<BOARD_ITEM*> lockedItems;
161 std::vector<BOARD_ITEM*> itemsToAlign;
162
163 for( EDA_ITEM* item : selection )
164 {
165 BOARD_ITEM* boardItem = static_cast<BOARD_ITEM*>( item );
166
167 // We do not lock items in the footprint editor
168 if( boardItem->IsLocked() && m_frame->IsType( FRAME_PCB_EDITOR ) )
169 {
170 // Locking a pad but not the footprint means that we align the footprint using
171 // the pad position. So we test for footprint locking here
172 if( boardItem->Type() == PCB_PAD_T && !boardItem->GetParent()->IsLocked() )
173 {
174 itemsToAlign.push_back( boardItem );
175 }
176 else
177 {
178 lockedItems.push_back( boardItem );
179 }
180 }
181 else
182 itemsToAlign.push_back( boardItem );
183 }
184
185 aItemsToAlign = GetBoundingBoxes( itemsToAlign );
186 aLockedItems = GetBoundingBoxes( lockedItems );
187 std::sort( aItemsToAlign.begin(), aItemsToAlign.end(), aCompare );
188 std::sort( aLockedItems.begin(), aLockedItems.end(), aCompare );
189
190 return aItemsToAlign.size();
191 }
192
193
AlignTop(const TOOL_EVENT & aEvent)194 int ALIGN_DISTRIBUTE_TOOL::AlignTop( const TOOL_EVENT& aEvent )
195 {
196 ALIGNMENT_RECTS itemsToAlign;
197 ALIGNMENT_RECTS locked_items;
198
199 if( !GetSelections( itemsToAlign, locked_items,
200 []( const ALIGNMENT_RECT left, const ALIGNMENT_RECT right)
201 {
202 return ( left.second.GetTop() < right.second.GetTop() );
203 } ) )
204 {
205 return 0;
206 }
207
208 BOARD_COMMIT commit( m_frame );
209
210 int targetTop = selectTarget( itemsToAlign, locked_items,
211 []( const ALIGNMENT_RECT& aVal )
212 {
213 return aVal.second.GetTop();
214 } );
215
216 // Move the selected items
217 for( std::pair<BOARD_ITEM*, EDA_RECT>& i : itemsToAlign )
218 {
219 BOARD_ITEM* item = i.first;
220 int difference = targetTop - i.second.GetTop();
221
222 if( item->GetParent() && item->GetParent()->IsSelected() )
223 continue;
224
225 // Don't move a pad by itself unless editing the footprint
226 if( item->Type() == PCB_PAD_T && m_frame->IsType( FRAME_PCB_EDITOR ) )
227 item = item->GetParent();
228
229 commit.Stage( item, CHT_MODIFY );
230 item->Move( wxPoint( 0, difference ) );
231 }
232
233 commit.Push( _( "Align to top" ) );
234
235 return 0;
236 }
237
238
AlignBottom(const TOOL_EVENT & aEvent)239 int ALIGN_DISTRIBUTE_TOOL::AlignBottom( const TOOL_EVENT& aEvent )
240 {
241 ALIGNMENT_RECTS itemsToAlign;
242 ALIGNMENT_RECTS locked_items;
243
244 if( !GetSelections( itemsToAlign, locked_items,
245 []( const ALIGNMENT_RECT left, const ALIGNMENT_RECT right)
246 {
247 return ( left.second.GetBottom() < right.second.GetBottom() );
248 } ) )
249 {
250 return 0;
251 }
252
253 BOARD_COMMIT commit( m_frame );
254
255 int targetBottom = selectTarget( itemsToAlign, locked_items,
256 []( const ALIGNMENT_RECT& aVal )
257 {
258 return aVal.second.GetBottom();
259 } );
260
261 // Move the selected items
262 for( std::pair<BOARD_ITEM*, EDA_RECT>& i : itemsToAlign )
263 {
264 int difference = targetBottom - i.second.GetBottom();
265 BOARD_ITEM* item = i.first;
266
267 if( item->GetParent() && item->GetParent()->IsSelected() )
268 continue;
269
270 // Don't move a pad by itself unless editing the footprint
271 if( item->Type() == PCB_PAD_T && m_frame->IsType( FRAME_PCB_EDITOR ) )
272 item = item->GetParent();
273
274 commit.Stage( item, CHT_MODIFY );
275 item->Move( wxPoint( 0, difference ) );
276 }
277
278 commit.Push( _( "Align to bottom" ) );
279
280 return 0;
281 }
282
283
AlignLeft(const TOOL_EVENT & aEvent)284 int ALIGN_DISTRIBUTE_TOOL::AlignLeft( const TOOL_EVENT& aEvent )
285 {
286 // Because this tool uses bounding boxes and they aren't mirrored even when
287 // the view is mirrored, we need to call the other one if mirrored.
288 if( getView()->IsMirroredX() )
289 {
290 return doAlignRight();
291 }
292 else
293 {
294 return doAlignLeft();
295 }
296 }
297
298
doAlignLeft()299 int ALIGN_DISTRIBUTE_TOOL::doAlignLeft()
300 {
301 ALIGNMENT_RECTS itemsToAlign;
302 ALIGNMENT_RECTS locked_items;
303
304 if( !GetSelections( itemsToAlign, locked_items,
305 []( const ALIGNMENT_RECT left, const ALIGNMENT_RECT right)
306 {
307 return ( left.second.GetLeft() < right.second.GetLeft() );
308 } ) )
309 {
310 return 0;
311 }
312
313 BOARD_COMMIT commit( m_frame );
314
315 int targetLeft = selectTarget( itemsToAlign, locked_items,
316 []( const ALIGNMENT_RECT& aVal )
317 {
318 return aVal.second.GetLeft();
319 } );
320
321 // Move the selected items
322 for( std::pair<BOARD_ITEM*, EDA_RECT>& i : itemsToAlign )
323 {
324 int difference = targetLeft - i.second.GetLeft();
325 BOARD_ITEM* item = i.first;
326
327 if( item->GetParent() && item->GetParent()->IsSelected() )
328 continue;
329
330 // Don't move a pad by itself unless editing the footprint
331 if( item->Type() == PCB_PAD_T && m_frame->IsType( FRAME_PCB_EDITOR ) )
332 item = item->GetParent();
333
334 commit.Stage( item, CHT_MODIFY );
335 item->Move( wxPoint( difference, 0 ) );
336 }
337
338 commit.Push( _( "Align to left" ) );
339
340 return 0;
341 }
342
343
AlignRight(const TOOL_EVENT & aEvent)344 int ALIGN_DISTRIBUTE_TOOL::AlignRight( const TOOL_EVENT& aEvent )
345 {
346 // Because this tool uses bounding boxes and they aren't mirrored even when
347 // the view is mirrored, we need to call the other one if mirrored.
348 if( getView()->IsMirroredX() )
349 {
350 return doAlignLeft();
351 }
352 else
353 {
354 return doAlignRight();
355 }
356 }
357
358
doAlignRight()359 int ALIGN_DISTRIBUTE_TOOL::doAlignRight()
360 {
361 ALIGNMENT_RECTS itemsToAlign;
362 ALIGNMENT_RECTS locked_items;
363
364 if( !GetSelections( itemsToAlign, locked_items,
365 []( const ALIGNMENT_RECT left, const ALIGNMENT_RECT right)
366 {
367 return ( left.second.GetRight() < right.second.GetRight() );
368 } ) )
369 {
370 return 0;
371 }
372
373 BOARD_COMMIT commit( m_frame );
374
375 int targetRight = selectTarget( itemsToAlign, locked_items,
376 []( const ALIGNMENT_RECT& aVal )
377 {
378 return aVal.second.GetRight();
379 } );
380
381 // Move the selected items
382 for( std::pair<BOARD_ITEM*, EDA_RECT>& i : itemsToAlign )
383 {
384 int difference = targetRight - i.second.GetRight();
385 BOARD_ITEM* item = i.first;
386
387 if( item->GetParent() && item->GetParent()->IsSelected() )
388 continue;
389
390 // Don't move a pad by itself unless editing the footprint
391 if( item->Type() == PCB_PAD_T && m_frame->IsType( FRAME_PCB_EDITOR ) )
392 item = item->GetParent();
393
394 commit.Stage( item, CHT_MODIFY );
395 item->Move( wxPoint( difference, 0 ) );
396 }
397
398 commit.Push( _( "Align to right" ) );
399
400 return 0;
401 }
402
403
AlignCenterX(const TOOL_EVENT & aEvent)404 int ALIGN_DISTRIBUTE_TOOL::AlignCenterX( const TOOL_EVENT& aEvent )
405 {
406 ALIGNMENT_RECTS itemsToAlign;
407 ALIGNMENT_RECTS locked_items;
408
409 if( !GetSelections( itemsToAlign, locked_items,
410 []( const ALIGNMENT_RECT left, const ALIGNMENT_RECT right)
411 {
412 return ( left.second.GetCenter().x < right.second.GetCenter().x );
413 } ) )
414 {
415 return 0;
416 }
417
418 BOARD_COMMIT commit( m_frame );
419
420 int targetX = selectTarget( itemsToAlign, locked_items,
421 []( const ALIGNMENT_RECT& aVal )
422 {
423 return aVal.second.GetCenter().x;
424 } );
425
426 // Move the selected items
427 for( std::pair<BOARD_ITEM*, EDA_RECT>& i : itemsToAlign )
428 {
429 int difference = targetX - i.second.GetCenter().x;
430 BOARD_ITEM* item = i.first;
431
432 if( item->GetParent() && item->GetParent()->IsSelected() )
433 continue;
434
435 // Don't move a pad by itself unless editing the footprint
436 if( item->Type() == PCB_PAD_T && m_frame->IsType( FRAME_PCB_EDITOR ) )
437 item = item->GetParent();
438
439 commit.Stage( item, CHT_MODIFY );
440 item->Move( wxPoint( difference, 0 ) );
441 }
442
443 commit.Push( _( "Align to middle" ) );
444
445 return 0;
446 }
447
448
AlignCenterY(const TOOL_EVENT & aEvent)449 int ALIGN_DISTRIBUTE_TOOL::AlignCenterY( const TOOL_EVENT& aEvent )
450 {
451 ALIGNMENT_RECTS itemsToAlign;
452 ALIGNMENT_RECTS locked_items;
453
454 if( !GetSelections( itemsToAlign, locked_items,
455 []( const ALIGNMENT_RECT left, const ALIGNMENT_RECT right)
456 {
457 return ( left.second.GetCenter().y < right.second.GetCenter().y );
458 } ) )
459 {
460 return 0;
461 }
462
463 BOARD_COMMIT commit( m_frame );
464
465 int targetY = selectTarget( itemsToAlign, locked_items,
466 []( const ALIGNMENT_RECT& aVal )
467 {
468 return aVal.second.GetCenter().y;
469 } );
470
471 // Move the selected items
472 for( std::pair<BOARD_ITEM*, EDA_RECT>& i : itemsToAlign )
473 {
474 int difference = targetY - i.second.GetCenter().y;
475 BOARD_ITEM* item = i.first;
476
477 if( item->GetParent() && item->GetParent()->IsSelected() )
478 continue;
479
480 // Don't move a pad by itself unless editing the footprint
481 if( item->Type() == PCB_PAD_T && m_frame->IsType( FRAME_PCB_EDITOR ) )
482 item = item->GetParent();
483
484 commit.Stage( item, CHT_MODIFY );
485 item->Move( wxPoint( 0, difference ) );
486 }
487
488 commit.Push( _( "Align to center" ) );
489
490 return 0;
491 }
492
493
DistributeHorizontally(const TOOL_EVENT & aEvent)494 int ALIGN_DISTRIBUTE_TOOL::DistributeHorizontally( const TOOL_EVENT& aEvent )
495 {
496 PCB_SELECTION& selection = m_selectionTool->RequestSelection(
497 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
498 {
499 // Iterate from the back so we don't have to worry about removals.
500 for( int i = aCollector.GetCount() - 1; i >= 0; --i )
501 {
502 BOARD_ITEM* item = aCollector[i];
503
504 if( item->Type() == PCB_MARKER_T )
505 aCollector.Remove( item );
506 }
507 },
508 m_frame->IsType( FRAME_PCB_EDITOR ) /* prompt user regarding locked items */ );
509
510 if( selection.Size() <= 1 )
511 return 0;
512
513 BOARD_COMMIT commit( m_frame );
514 ALIGNMENT_RECTS itemsToDistribute = GetBoundingBoxes( selection );
515
516 // find the last item by reverse sorting
517 std::sort( itemsToDistribute.begin(), itemsToDistribute.end(),
518 [] ( const ALIGNMENT_RECT left, const ALIGNMENT_RECT right)
519 {
520 return ( left.second.GetRight() > right.second.GetRight() );
521 } );
522
523 BOARD_ITEM* lastItem = itemsToDistribute.begin()->first;
524 const int maxRight = itemsToDistribute.begin()->second.GetRight();
525
526 // sort to get starting order
527 std::sort( itemsToDistribute.begin(), itemsToDistribute.end(),
528 [] ( const ALIGNMENT_RECT left, const ALIGNMENT_RECT right)
529 {
530 return ( left.second.GetX() < right.second.GetX() );
531 } );
532
533 const int minX = itemsToDistribute.begin()->second.GetX();
534 int totalGap = maxRight - minX;
535 int totalWidth = 0;
536
537 for( std::pair<BOARD_ITEM*, EDA_RECT>& i : itemsToDistribute )
538 totalWidth += i.second.GetWidth();
539
540 if( totalGap < totalWidth )
541 {
542 // the width of the items exceeds the gap (overlapping items) -> use center point spacing
543 doDistributeCentersHorizontally( itemsToDistribute, commit );
544 }
545 else
546 {
547 totalGap -= totalWidth;
548 doDistributeGapsHorizontally( itemsToDistribute, commit, lastItem, totalGap );
549 }
550
551 commit.Push( _( "Distribute horizontally" ) );
552
553 return 0;
554 }
555
556
doDistributeGapsHorizontally(ALIGNMENT_RECTS & itemsToDistribute,BOARD_COMMIT & aCommit,const BOARD_ITEM * lastItem,int totalGap) const557 void ALIGN_DISTRIBUTE_TOOL::doDistributeGapsHorizontally( ALIGNMENT_RECTS& itemsToDistribute,
558 BOARD_COMMIT& aCommit,
559 const BOARD_ITEM* lastItem,
560 int totalGap ) const
561 {
562 const int itemGap = totalGap / ( itemsToDistribute.size() - 1 );
563 int targetX = itemsToDistribute.begin()->second.GetX();
564
565 for( std::pair<BOARD_ITEM*, EDA_RECT>& i : itemsToDistribute )
566 {
567 BOARD_ITEM* item = i.first;
568
569 // cover the corner case where the last item is wider than the previous item and gap
570 if( lastItem == item )
571 continue;
572
573 if( item->GetParent() && item->GetParent()->IsSelected() )
574 continue;
575
576 // Don't move a pad by itself unless editing the footprint
577 if( item->Type() == PCB_PAD_T && m_frame->IsType( FRAME_PCB_EDITOR ) )
578 item = item->GetParent();
579
580 int difference = targetX - i.second.GetX();
581 aCommit.Stage( item, CHT_MODIFY );
582 item->Move( wxPoint( difference, 0 ) );
583 targetX += ( i.second.GetWidth() + itemGap );
584 }
585 }
586
587
doDistributeCentersHorizontally(ALIGNMENT_RECTS & itemsToDistribute,BOARD_COMMIT & aCommit) const588 void ALIGN_DISTRIBUTE_TOOL::doDistributeCentersHorizontally( ALIGNMENT_RECTS &itemsToDistribute,
589 BOARD_COMMIT& aCommit ) const
590 {
591 std::sort( itemsToDistribute.begin(), itemsToDistribute.end(),
592 [] ( const ALIGNMENT_RECT left, const ALIGNMENT_RECT right)
593 {
594 return ( left.second.GetCenter().x < right.second.GetCenter().x );
595 } );
596
597 const int totalGap = ( itemsToDistribute.end()-1 )->second.GetCenter().x
598 - itemsToDistribute.begin()->second.GetCenter().x;
599 const int itemGap = totalGap / ( itemsToDistribute.size() - 1 );
600 int targetX = itemsToDistribute.begin()->second.GetCenter().x;
601
602 for( std::pair<BOARD_ITEM*, EDA_RECT>& i : itemsToDistribute )
603 {
604 BOARD_ITEM* item = i.first;
605
606 if( item->GetParent() && item->GetParent()->IsSelected() )
607 continue;
608
609 // Don't move a pad by itself unless editing the footprint
610 if( item->Type() == PCB_PAD_T && m_frame->IsType( FRAME_PCB_EDITOR ) )
611 item = item->GetParent();
612
613 int difference = targetX - i.second.GetCenter().x;
614 aCommit.Stage( item, CHT_MODIFY );
615 item->Move( wxPoint( difference, 0 ) );
616 targetX += ( itemGap );
617 }
618 }
619
620
DistributeVertically(const TOOL_EVENT & aEvent)621 int ALIGN_DISTRIBUTE_TOOL::DistributeVertically( const TOOL_EVENT& aEvent )
622 {
623 PCB_SELECTION& selection = m_selectionTool->RequestSelection(
624 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
625 {
626 // Iterate from the back so we don't have to worry about removals.
627 for( int i = aCollector.GetCount() - 1; i >= 0; --i )
628 {
629 BOARD_ITEM* item = aCollector[i];
630
631 if( item->Type() == PCB_MARKER_T )
632 aCollector.Remove( item );
633 }
634 },
635 m_frame->IsType( FRAME_PCB_EDITOR ) /* prompt user regarding locked items */ );
636
637 if( selection.Size() <= 1 )
638 return 0;
639
640 BOARD_COMMIT commit( m_frame );
641 ALIGNMENT_RECTS itemsToDistribute = GetBoundingBoxes( selection );
642
643 // find the last item by reverse sorting
644 std::sort( itemsToDistribute.begin(), itemsToDistribute.end(),
645 [] ( const ALIGNMENT_RECT left, const ALIGNMENT_RECT right)
646 {
647 return ( left.second.GetBottom() > right.second.GetBottom() );
648 } );
649
650 BOARD_ITEM* lastItem = itemsToDistribute.begin()->first;
651 const int maxBottom = itemsToDistribute.begin()->second.GetBottom();
652
653 // sort to get starting order
654 std::sort( itemsToDistribute.begin(), itemsToDistribute.end(),
655 [] ( const ALIGNMENT_RECT left, const ALIGNMENT_RECT right)
656 {
657 return ( left.second.GetCenter().y < right.second.GetCenter().y );
658 } );
659
660 int minY = itemsToDistribute.begin()->second.GetY();
661 int totalGap = maxBottom - minY;
662 int totalHeight = 0;
663
664 for( std::pair<BOARD_ITEM*, EDA_RECT>& i : itemsToDistribute )
665 totalHeight += i.second.GetHeight();
666
667 if( totalGap < totalHeight )
668 {
669 // the width of the items exceeds the gap (overlapping items) -> use center point spacing
670 doDistributeCentersVertically( itemsToDistribute, commit );
671 }
672 else
673 {
674 totalGap -= totalHeight;
675 doDistributeGapsVertically( itemsToDistribute, commit, lastItem, totalGap );
676 }
677
678 commit.Push( _( "Distribute vertically" ) );
679
680 return 0;
681 }
682
683
doDistributeGapsVertically(ALIGNMENT_RECTS & itemsToDistribute,BOARD_COMMIT & aCommit,const BOARD_ITEM * lastItem,int totalGap) const684 void ALIGN_DISTRIBUTE_TOOL::doDistributeGapsVertically( ALIGNMENT_RECTS& itemsToDistribute,
685 BOARD_COMMIT& aCommit,
686 const BOARD_ITEM* lastItem,
687 int totalGap ) const
688 {
689 const int itemGap = totalGap / ( itemsToDistribute.size() - 1 );
690 int targetY = itemsToDistribute.begin()->second.GetY();
691
692 for( std::pair<BOARD_ITEM*, EDA_RECT>& i : itemsToDistribute )
693 {
694 BOARD_ITEM* item = i.first;
695
696 // cover the corner case where the last item is wider than the previous item and gap
697 if( lastItem == item )
698 continue;
699
700 if( item->GetParent() && item->GetParent()->IsSelected() )
701 continue;
702
703 // Don't move a pad by itself unless editing the footprint
704 if( item->Type() == PCB_PAD_T && m_frame->IsType( FRAME_PCB_EDITOR ) )
705 item = item->GetParent();
706
707 int difference = targetY - i.second.GetY();
708 aCommit.Stage( item, CHT_MODIFY );
709 item->Move( wxPoint( 0, difference ) );
710 targetY += ( i.second.GetHeight() + itemGap );
711 }
712 }
713
714
doDistributeCentersVertically(ALIGNMENT_RECTS & itemsToDistribute,BOARD_COMMIT & aCommit) const715 void ALIGN_DISTRIBUTE_TOOL::doDistributeCentersVertically( ALIGNMENT_RECTS& itemsToDistribute,
716 BOARD_COMMIT& aCommit ) const
717 {
718 std::sort( itemsToDistribute.begin(), itemsToDistribute.end(),
719 [] ( const ALIGNMENT_RECT left, const ALIGNMENT_RECT right)
720 {
721 return ( left.second.GetCenter().y < right.second.GetCenter().y );
722 } );
723
724 const int totalGap = ( itemsToDistribute.end()-1 )->second.GetCenter().y
725 - itemsToDistribute.begin()->second.GetCenter().y;
726 const int itemGap = totalGap / ( itemsToDistribute.size() - 1 );
727 int targetY = itemsToDistribute.begin()->second.GetCenter().y;
728
729 for( std::pair<BOARD_ITEM*, EDA_RECT>& i : itemsToDistribute )
730 {
731 BOARD_ITEM* item = i.first;
732
733 if( item->GetParent() && item->GetParent()->IsSelected() )
734 continue;
735
736 // Don't move a pad by itself unless editing the footprint
737 if( item->Type() == PCB_PAD_T && m_frame->IsType( FRAME_PCB_EDITOR ) )
738 item = item->GetParent();
739
740 int difference = targetY - i.second.GetCenter().y;
741 aCommit.Stage( item, CHT_MODIFY );
742 item->Move( wxPoint( 0, difference ) );
743 targetY += ( itemGap );
744 }
745 }
746
747
setTransitions()748 void ALIGN_DISTRIBUTE_TOOL::setTransitions()
749 {
750 Go( &ALIGN_DISTRIBUTE_TOOL::AlignTop, PCB_ACTIONS::alignTop.MakeEvent() );
751 Go( &ALIGN_DISTRIBUTE_TOOL::AlignBottom, PCB_ACTIONS::alignBottom.MakeEvent() );
752 Go( &ALIGN_DISTRIBUTE_TOOL::AlignLeft, PCB_ACTIONS::alignLeft.MakeEvent() );
753 Go( &ALIGN_DISTRIBUTE_TOOL::AlignRight, PCB_ACTIONS::alignRight.MakeEvent() );
754 Go( &ALIGN_DISTRIBUTE_TOOL::AlignCenterX, PCB_ACTIONS::alignCenterX.MakeEvent() );
755 Go( &ALIGN_DISTRIBUTE_TOOL::AlignCenterY, PCB_ACTIONS::alignCenterY.MakeEvent() );
756
757 Go( &ALIGN_DISTRIBUTE_TOOL::DistributeHorizontally,
758 PCB_ACTIONS::distributeHorizontally.MakeEvent() );
759 Go( &ALIGN_DISTRIBUTE_TOOL::DistributeVertically,
760 PCB_ACTIONS::distributeVertically.MakeEvent() );
761 }
762