pcbnew: Check locks in alignment

When aligning in pcbnew, check for pad/module locks before performing a
move and query the user.

When aligning on pads, don't move the pad without moving the footprint,
so we don't break footprints.

Fixes: lp:1751352
* https://bugs.launchpad.net/kicad/+bug/1751352
This commit is contained in:
Seth Hillbrand 2018-02-28 15:13:21 -08:00
parent e6245c5c5a
commit d5cd9761dd
2 changed files with 160 additions and 34 deletions

View File

@ -182,19 +182,69 @@ ALIGNMENT_RECTS GetBoundingBoxes( const SELECTION &sel )
} }
int ALIGN_DISTRIBUTE_TOOL::checkLockedStatus( const SELECTION &selection ) const
{
SELECTION moving_items( selection );
// Remove the anchor from the list
moving_items.Remove( moving_items.Front() );
bool containsLocked = false;
// Check if the selection contains locked items
for( const auto& item : moving_items )
{
switch ( item->Type() )
{
case PCB_MODULE_T:
if( static_cast< MODULE* >( item )->IsLocked() )
containsLocked = true;
break;
case PCB_PAD_T:
case PCB_MODULE_EDGE_T:
case PCB_MODULE_TEXT_T:
if( static_cast< MODULE* >( item->GetParent() )->IsLocked() )
containsLocked = true;
break;
default: // suppress warnings
break;
}
}
if( containsLocked )
{
if( IsOK( getEditFrame< PCB_EDIT_FRAME >(),
_( "Selection contains locked items. Do you want to continue?" ) ) )
{
return SELECTION_LOCK_OVERRIDE;
}
else
return SELECTION_LOCKED;
}
return SELECTION_UNLOCKED;
}
int ALIGN_DISTRIBUTE_TOOL::AlignTop( const TOOL_EVENT& aEvent ) int ALIGN_DISTRIBUTE_TOOL::AlignTop( const TOOL_EVENT& aEvent )
{ {
const SELECTION& selection = m_selectionTool->GetSelection(); auto frame = getEditFrame<PCB_BASE_FRAME>();
const SELECTION& selection = m_selectionTool->RequestSelection( SELECTION_EDITABLE );
if( selection.Size() <= 1 ) if( selection.Size() <= 1 )
return 0; return 0;
BOARD_COMMIT commit( getEditFrame<PCB_BASE_FRAME>() );
commit.StageItems( selection, CHT_MODIFY );
auto itemsToAlign = GetBoundingBoxes( selection ); auto itemsToAlign = GetBoundingBoxes( selection );
std::sort( itemsToAlign.begin(), itemsToAlign.end(), SortTopmostY ); std::sort( itemsToAlign.begin(), itemsToAlign.end(), SortTopmostY );
if( checkLockedStatus( selection ) == SELECTION_LOCKED )
return 0;
BOARD_COMMIT commit( frame );
commit.StageItems( selection, CHT_MODIFY );
// after sorting, the fist item acts as the target for all others // after sorting, the fist item acts as the target for all others
const int targetTop = itemsToAlign.begin()->second.GetY(); const int targetTop = itemsToAlign.begin()->second.GetY();
@ -202,7 +252,13 @@ int ALIGN_DISTRIBUTE_TOOL::AlignTop( const TOOL_EVENT& aEvent )
for( auto& i : itemsToAlign ) for( auto& i : itemsToAlign )
{ {
int difference = targetTop - i.second.GetY(); int difference = targetTop - i.second.GetY();
i.first->Move( wxPoint( 0, difference ) ); BOARD_ITEM* item = i.first;
// Don't move a pad by itself unless editing the footprint
if( item->Type() == PCB_PAD_T && frame->IsType( FRAME_PCB ) )
item = item->GetParent();
item->Move( wxPoint( 0, difference ) );
} }
commit.Push( _( "Align to top" ) ); commit.Push( _( "Align to top" ) );
@ -213,17 +269,21 @@ int ALIGN_DISTRIBUTE_TOOL::AlignTop( const TOOL_EVENT& aEvent )
int ALIGN_DISTRIBUTE_TOOL::AlignBottom( const TOOL_EVENT& aEvent ) int ALIGN_DISTRIBUTE_TOOL::AlignBottom( const TOOL_EVENT& aEvent )
{ {
const SELECTION& selection = m_selectionTool->GetSelection(); auto frame = getEditFrame<PCB_BASE_FRAME>();
const SELECTION& selection = m_selectionTool->RequestSelection( SELECTION_EDITABLE );
if( selection.Size() <= 1 ) if( selection.Size() <= 1 )
return 0; return 0;
BOARD_COMMIT commit( getEditFrame<PCB_BASE_FRAME>() );
commit.StageItems( selection, CHT_MODIFY );
auto itemsToAlign = GetBoundingBoxes( selection ); auto itemsToAlign = GetBoundingBoxes( selection );
std::sort( itemsToAlign.begin(), itemsToAlign.end(), SortBottommostY ); std::sort( itemsToAlign.begin(), itemsToAlign.end(), SortBottommostY );
if( checkLockedStatus( selection ) == SELECTION_LOCKED )
return 0;
BOARD_COMMIT commit( frame );
commit.StageItems( selection, CHT_MODIFY );
// after sorting, the fist item acts as the target for all others // after sorting, the fist item acts as the target for all others
const int targetBottom = itemsToAlign.begin()->second.GetBottom(); const int targetBottom = itemsToAlign.begin()->second.GetBottom();
@ -231,7 +291,13 @@ int ALIGN_DISTRIBUTE_TOOL::AlignBottom( const TOOL_EVENT& aEvent )
for( auto& i : itemsToAlign ) for( auto& i : itemsToAlign )
{ {
int difference = targetBottom - i.second.GetBottom(); int difference = targetBottom - i.second.GetBottom();
i.first->Move( wxPoint( 0, difference ) ); BOARD_ITEM* item = i.first;
// Don't move a pad by itself unless editing the footprint
if( item->Type() == PCB_PAD_T && frame->IsType( FRAME_PCB ) )
item = item->GetParent();
item->Move( wxPoint( 0, difference ) );
} }
commit.Push( _( "Align to bottom" ) ); commit.Push( _( "Align to bottom" ) );
@ -257,17 +323,21 @@ int ALIGN_DISTRIBUTE_TOOL::AlignLeft( const TOOL_EVENT& aEvent )
int ALIGN_DISTRIBUTE_TOOL::doAlignLeft() int ALIGN_DISTRIBUTE_TOOL::doAlignLeft()
{ {
const SELECTION& selection = m_selectionTool->GetSelection(); auto frame = getEditFrame<PCB_BASE_FRAME>();
const SELECTION& selection = m_selectionTool->RequestSelection( SELECTION_EDITABLE );
if( selection.Size() <= 1 ) if( selection.Size() <= 1 )
return 0; return 0;
BOARD_COMMIT commit( getEditFrame<PCB_BASE_FRAME>() );
commit.StageItems( selection, CHT_MODIFY );
auto itemsToAlign = GetBoundingBoxes( selection ); auto itemsToAlign = GetBoundingBoxes( selection );
std::sort( itemsToAlign.begin(), itemsToAlign.end(), SortLeftmostX ); std::sort( itemsToAlign.begin(), itemsToAlign.end(), SortLeftmostX );
if( checkLockedStatus( selection ) == SELECTION_LOCKED )
return 0;
BOARD_COMMIT commit( frame );
commit.StageItems( selection, CHT_MODIFY );
// after sorting, the fist item acts as the target for all others // after sorting, the fist item acts as the target for all others
const int targetLeft = itemsToAlign.begin()->second.GetX(); const int targetLeft = itemsToAlign.begin()->second.GetX();
@ -275,7 +345,13 @@ int ALIGN_DISTRIBUTE_TOOL::doAlignLeft()
for( auto& i : itemsToAlign ) for( auto& i : itemsToAlign )
{ {
int difference = targetLeft - i.second.GetX(); int difference = targetLeft - i.second.GetX();
i.first->Move( wxPoint( difference, 0 ) ); BOARD_ITEM* item = i.first;
// Don't move a pad by itself unless editing the footprint
if( item->Type() == PCB_PAD_T && frame->IsType( FRAME_PCB ) )
item = item->GetParent();
item->Move( wxPoint( difference, 0 ) );
} }
commit.Push( _( "Align to left" ) ); commit.Push( _( "Align to left" ) );
@ -301,17 +377,21 @@ int ALIGN_DISTRIBUTE_TOOL::AlignRight( const TOOL_EVENT& aEvent )
int ALIGN_DISTRIBUTE_TOOL::doAlignRight() int ALIGN_DISTRIBUTE_TOOL::doAlignRight()
{ {
const SELECTION& selection = m_selectionTool->GetSelection(); auto frame = getEditFrame<PCB_BASE_FRAME>();
const SELECTION& selection = m_selectionTool->RequestSelection( SELECTION_EDITABLE );
if( selection.Size() <= 1 ) if( selection.Size() <= 1 )
return 0; return 0;
BOARD_COMMIT commit( getEditFrame<PCB_BASE_FRAME>() );
commit.StageItems( selection, CHT_MODIFY );
auto itemsToAlign = GetBoundingBoxes( selection ); auto itemsToAlign = GetBoundingBoxes( selection );
std::sort( itemsToAlign.begin(), itemsToAlign.end(), SortRightmostX ); std::sort( itemsToAlign.begin(), itemsToAlign.end(), SortRightmostX );
if( checkLockedStatus( selection ) == SELECTION_LOCKED )
return 0;
BOARD_COMMIT commit( frame );
commit.StageItems( selection, CHT_MODIFY );
// after sorting, the fist item acts as the target for all others // after sorting, the fist item acts as the target for all others
const int targetRight = itemsToAlign.begin()->second.GetRight(); const int targetRight = itemsToAlign.begin()->second.GetRight();
@ -319,7 +399,13 @@ int ALIGN_DISTRIBUTE_TOOL::doAlignRight()
for( auto& i : itemsToAlign ) for( auto& i : itemsToAlign )
{ {
int difference = targetRight - i.second.GetRight(); int difference = targetRight - i.second.GetRight();
i.first->Move( wxPoint( difference, 0 ) ); BOARD_ITEM* item = i.first;
// Don't move a pad by itself unless editing the footprint
if( item->Type() == PCB_PAD_T && frame->IsType( FRAME_PCB ) )
item = item->GetParent();
item->Move( wxPoint( difference, 0 ) );
} }
commit.Push( _( "Align to right" ) ); commit.Push( _( "Align to right" ) );
@ -330,17 +416,21 @@ int ALIGN_DISTRIBUTE_TOOL::doAlignRight()
int ALIGN_DISTRIBUTE_TOOL::AlignCenterX( const TOOL_EVENT& aEvent ) int ALIGN_DISTRIBUTE_TOOL::AlignCenterX( const TOOL_EVENT& aEvent )
{ {
const SELECTION& selection = m_selectionTool->GetSelection(); auto frame = getEditFrame<PCB_BASE_FRAME>();
const SELECTION& selection = m_selectionTool->RequestSelection( SELECTION_EDITABLE );
if( selection.Size() <= 1 ) if( selection.Size() <= 1 )
return 0; return 0;
BOARD_COMMIT commit( getEditFrame<PCB_BASE_FRAME>() );
commit.StageItems( selection, CHT_MODIFY );
auto itemsToAlign = GetBoundingBoxes( selection ); auto itemsToAlign = GetBoundingBoxes( selection );
std::sort( itemsToAlign.begin(), itemsToAlign.end(), SortCenterX ); std::sort( itemsToAlign.begin(), itemsToAlign.end(), SortCenterX );
if( checkLockedStatus( selection ) == SELECTION_LOCKED )
return 0;
BOARD_COMMIT commit( frame );
commit.StageItems( selection, CHT_MODIFY );
// after sorting use the x coordinate of the middle item as a target for all other items // after sorting use the x coordinate of the middle item as a target for all other items
const int targetX = itemsToAlign.at( itemsToAlign.size() / 2 ).second.GetCenter().x; const int targetX = itemsToAlign.at( itemsToAlign.size() / 2 ).second.GetCenter().x;
@ -348,7 +438,13 @@ int ALIGN_DISTRIBUTE_TOOL::AlignCenterX( const TOOL_EVENT& aEvent )
for( auto& i : itemsToAlign ) for( auto& i : itemsToAlign )
{ {
int difference = targetX - i.second.GetCenter().x; int difference = targetX - i.second.GetCenter().x;
i.first->Move( wxPoint( difference, 0 ) ); BOARD_ITEM* item = i.first;
// Don't move a pad by itself unless editing the footprint
if( item->Type() == PCB_PAD_T && frame->IsType( FRAME_PCB ) )
item = item->GetParent();
item->Move( wxPoint( difference, 0 ) );
} }
commit.Push( _( "Align to middle" ) ); commit.Push( _( "Align to middle" ) );
@ -359,17 +455,21 @@ int ALIGN_DISTRIBUTE_TOOL::AlignCenterX( const TOOL_EVENT& aEvent )
int ALIGN_DISTRIBUTE_TOOL::AlignCenterY( const TOOL_EVENT& aEvent ) int ALIGN_DISTRIBUTE_TOOL::AlignCenterY( const TOOL_EVENT& aEvent )
{ {
const SELECTION& selection = m_selectionTool->GetSelection(); auto frame = getEditFrame<PCB_BASE_FRAME>();
const SELECTION& selection = m_selectionTool->RequestSelection( SELECTION_EDITABLE );
if( selection.Size() <= 1 ) if( selection.Size() <= 1 )
return 0; return 0;
BOARD_COMMIT commit( getEditFrame<PCB_BASE_FRAME>() );
commit.StageItems( selection, CHT_MODIFY );
auto itemsToAlign = GetBoundingBoxes( selection ); auto itemsToAlign = GetBoundingBoxes( selection );
std::sort( itemsToAlign.begin(), itemsToAlign.end(), SortCenterY ); std::sort( itemsToAlign.begin(), itemsToAlign.end(), SortCenterY );
if( checkLockedStatus( selection ) == SELECTION_LOCKED )
return 0;
BOARD_COMMIT commit( frame );
commit.StageItems( selection, CHT_MODIFY );
// after sorting use the y coordinate of the middle item as a target for all other items // after sorting use the y coordinate of the middle item as a target for all other items
const int targetY = itemsToAlign.at( itemsToAlign.size() / 2 ).second.GetCenter().y; const int targetY = itemsToAlign.at( itemsToAlign.size() / 2 ).second.GetCenter().y;
@ -377,7 +477,13 @@ int ALIGN_DISTRIBUTE_TOOL::AlignCenterY( const TOOL_EVENT& aEvent )
for( auto& i : itemsToAlign ) for( auto& i : itemsToAlign )
{ {
int difference = targetY - i.second.GetCenter().y; int difference = targetY - i.second.GetCenter().y;
i.first->Move( wxPoint( 0, difference ) ); BOARD_ITEM* item = i.first;
// Don't move a pad by itself unless editing the footprint
if( item->Type() == PCB_PAD_T && frame->IsType( FRAME_PCB ) )
item = item->GetParent();
item->Move( wxPoint( 0, difference ) );
} }
commit.Push( _( "Align to center" ) ); commit.Push( _( "Align to center" ) );
@ -388,12 +494,17 @@ int ALIGN_DISTRIBUTE_TOOL::AlignCenterY( const TOOL_EVENT& aEvent )
int ALIGN_DISTRIBUTE_TOOL::DistributeHorizontally( const TOOL_EVENT& aEvent ) int ALIGN_DISTRIBUTE_TOOL::DistributeHorizontally( const TOOL_EVENT& aEvent )
{ {
const SELECTION& selection = m_selectionTool->GetSelection(); auto frame = getEditFrame<PCB_BASE_FRAME>();
const SELECTION& selection = m_selectionTool->RequestSelection(
SELECTION_EDITABLE | SELECTION_SANITIZE_PADS );
if( selection.Size() <= 1 ) if( selection.Size() <= 1 )
return 0; return 0;
BOARD_COMMIT commit( getEditFrame<PCB_BASE_FRAME>() ); if( m_selectionTool->CheckLock() == SELECTION_LOCKED )
return 0;
BOARD_COMMIT commit( frame );
commit.StageItems( selection, CHT_MODIFY ); commit.StageItems( selection, CHT_MODIFY );
auto itemsToDistribute = GetBoundingBoxes( selection ); auto itemsToDistribute = GetBoundingBoxes( selection );
@ -470,12 +581,17 @@ void ALIGN_DISTRIBUTE_TOOL::doDistributeCentersHorizontally( ALIGNMENT_RECTS &it
int ALIGN_DISTRIBUTE_TOOL::DistributeVertically( const TOOL_EVENT& aEvent ) int ALIGN_DISTRIBUTE_TOOL::DistributeVertically( const TOOL_EVENT& aEvent )
{ {
const SELECTION& selection = m_selectionTool->GetSelection(); auto frame = getEditFrame<PCB_BASE_FRAME>();
const SELECTION& selection = m_selectionTool->RequestSelection(
SELECTION_EDITABLE | SELECTION_SANITIZE_PADS );
if( selection.Size() <= 1 ) if( selection.Size() <= 1 )
return 0; return 0;
BOARD_COMMIT commit( getEditFrame<PCB_BASE_FRAME>() ); if( m_selectionTool->CheckLock() == SELECTION_LOCKED )
return 0;
BOARD_COMMIT commit( frame );
commit.StageItems( selection, CHT_MODIFY ); commit.StageItems( selection, CHT_MODIFY );
auto itemsToDistribute = GetBoundingBoxes( selection ); auto itemsToDistribute = GetBoundingBoxes( selection );

View File

@ -119,6 +119,16 @@ private:
CONTEXT_MENU* m_placementMenu; CONTEXT_MENU* m_placementMenu;
/**
* Check a selection to ensure locks are valid for alignment.
*
* This is slightly different from the standard lock checking in that we ignore the lock
* of the first element in the selection as this is meant to be our anchor.
* We also check the lock of a pad's parent as we will not move pads independently of
* the parent module
*/
int checkLockedStatus( const SELECTION &selection ) const;
/** /**
* Distributes selected items using an even spacing between the centers of their bounding boxes * Distributes selected items using an even spacing between the centers of their bounding boxes
* *