pcbnew: Align/Distribute handle locking

This fixes the handling of align/distribute tool when called on locked
items.  Locked items cannot be moved but they may be used for the target
of an align/distribute operation.

Fixes: lp:1808238
* https://bugs.launchpad.net/kicad/+bug/1808238
This commit is contained in:
Seth Hillbrand 2018-12-14 05:22:00 -07:00
parent 0a26388901
commit 7e9fee285f
2 changed files with 142 additions and 121 deletions

View File

@ -127,87 +127,106 @@ bool ALIGN_DISTRIBUTE_TOOL::Init()
}
bool SortLeftmostX( const std::pair<BOARD_ITEM*, EDA_RECT> left, const std::pair<BOARD_ITEM*, EDA_RECT> right)
template <class T>
ALIGNMENT_RECTS GetBoundingBoxes( const T &sel )
{
return ( left.second.GetX() < right.second.GetX() );
}
bool SortRightmostX( const std::pair<BOARD_ITEM*, EDA_RECT> left, const std::pair<BOARD_ITEM*, EDA_RECT> right)
{
return ( left.second.GetRight() > right.second.GetRight() );
}
bool SortTopmostY( const std::pair<BOARD_ITEM*, EDA_RECT> left, const std::pair<BOARD_ITEM*, EDA_RECT> right)
{
return ( left.second.GetY() < right.second.GetY() );
}
bool SortCenterX( const std::pair<BOARD_ITEM*, EDA_RECT> left, const std::pair<BOARD_ITEM*, EDA_RECT> right)
{
return ( left.second.GetCenter().x < right.second.GetCenter().x );
}
bool SortCenterY( const std::pair<BOARD_ITEM*, EDA_RECT> left, const std::pair<BOARD_ITEM*, EDA_RECT> right)
{
return ( left.second.GetCenter().y < right.second.GetCenter().y );
}
bool SortBottommostY( const std::pair<BOARD_ITEM*, EDA_RECT> left, const std::pair<BOARD_ITEM*, EDA_RECT> right)
{
return ( left.second.GetBottom() > right.second.GetBottom() );
}
ALIGNMENT_RECTS GetBoundingBoxes( const SELECTION &sel )
{
const SELECTION& selection = sel;
ALIGNMENT_RECTS rects;
for( auto item : selection )
for( auto item : sel )
{
BOARD_ITEM* boardItem = static_cast<BOARD_ITEM*>( item );
if( item->Type() == PCB_MODULE_T )
{
rects.emplace_back( std::make_pair( boardItem, static_cast<MODULE*>( item )->GetFootprintRect() ) );
}
else
{
rects.emplace_back( std::make_pair( boardItem, item->GetBoundingBox() ) );
}
}
return rects;
}
template< typename T >
int ALIGN_DISTRIBUTE_TOOL::selectTarget( ALIGNMENT_RECTS& aItems, ALIGNMENT_RECTS& aLocked, T aGetValue )
{
wxPoint curPos( getViewControls()->GetCursorPosition().x, getViewControls()->GetCursorPosition().y );
// after sorting, the fist item acts as the target for all others
// unless we have a locked item, in which case we use that for the target
int target = !aLocked.size() ? aGetValue( aItems.front() ): aGetValue( aLocked.front() );
// Iterate through both lists to find if we are mouse-over on one of the items.
for( auto sel = aLocked.begin(); sel != aItems.end(); sel++ )
{
// If there are locked items, prefer aligning to them over
// aligning to the cursor as they do not move
if( sel == aLocked.end() )
{
if( aLocked.size() == 0 )
{
sel = aItems.begin();
continue;
}
break;
}
// We take the first target that overlaps our cursor.
// This is deterministic because we assume sorted arrays
if( sel->second.Contains( curPos ) )
{
target = aGetValue( *sel );
break;
}
}
return target;
}
template< typename T >
size_t ALIGN_DISTRIBUTE_TOOL::GetSelections( ALIGNMENT_RECTS& aItems, ALIGNMENT_RECTS& aLocked, T aCompare )
{
SELECTION& selection = m_selectionTool->RequestSelection(
[]( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector )
{ EditToolSelectionFilter( aCollector, EXCLUDE_TRANSIENTS ); } );
if( selection.Size() <= 1 )
return 0;
std::vector<BOARD_ITEM*> lockedItems;
selection = m_selectionTool->RequestSelection(
[]( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector )
{ EditToolSelectionFilter( aCollector, EXCLUDE_LOCKED ); }, &lockedItems );
aItems = GetBoundingBoxes( selection );
aLocked = GetBoundingBoxes( lockedItems );
std::sort( aItems.begin(), aItems.end(), aCompare );
std::sort( aLocked.begin(), aLocked.end(), aCompare );
return aItems.size();
}
int ALIGN_DISTRIBUTE_TOOL::AlignTop( const TOOL_EVENT& aEvent )
{
SELECTION& selection = m_selectionTool->RequestSelection(
[]( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector )
{ EditToolSelectionFilter( aCollector, EXCLUDE_LOCKED | EXCLUDE_TRANSIENTS ); } );
ALIGNMENT_RECTS itemsToAlign;
ALIGNMENT_RECTS locked_items;
if( selection.Size() <= 1 )
if( !GetSelections( itemsToAlign, locked_items, []( const ALIGNMENT_RECT left, const ALIGNMENT_RECT right)
{ return ( left.second.GetTop() < right.second.GetTop() ); } ) )
return 0;
auto itemsToAlign = GetBoundingBoxes( selection );
std::sort( itemsToAlign.begin(), itemsToAlign.end(), SortTopmostY );
BOARD_COMMIT commit( m_frame );
commit.StageItems( selection, CHT_MODIFY );
// after sorting, the fist item acts as the target for all others
const int targetTop = itemsToAlign.begin()->second.GetY();
commit.StageItems( m_selectionTool->GetSelection(), CHT_MODIFY );
auto targetTop = selectTarget( itemsToAlign, locked_items, []( const ALIGNMENT_RECT& aVal )
{ return aVal.second.GetTop(); } );
// Move the selected items
for( auto& i : itemsToAlign )
{
int difference = targetTop - i.second.GetY();
int difference = targetTop - i.second.GetTop();
BOARD_ITEM* item = i.first;
// Don't move a pad by itself unless editing the footprint
@ -225,21 +244,17 @@ int ALIGN_DISTRIBUTE_TOOL::AlignTop( const TOOL_EVENT& aEvent )
int ALIGN_DISTRIBUTE_TOOL::AlignBottom( const TOOL_EVENT& aEvent )
{
SELECTION& selection = m_selectionTool->RequestSelection(
[]( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector )
{ EditToolSelectionFilter( aCollector, EXCLUDE_LOCKED | EXCLUDE_TRANSIENTS ); } );
ALIGNMENT_RECTS itemsToAlign;
ALIGNMENT_RECTS locked_items;
if( selection.Size() <= 1 )
if( !GetSelections( itemsToAlign, locked_items, []( const ALIGNMENT_RECT left, const ALIGNMENT_RECT right)
{ return ( left.second.GetBottom() < right.second.GetBottom() ); } ) )
return 0;
auto itemsToAlign = GetBoundingBoxes( selection );
std::sort( itemsToAlign.begin(), itemsToAlign.end(), SortBottommostY );
BOARD_COMMIT commit( m_frame );
commit.StageItems( selection, CHT_MODIFY );
// after sorting, the fist item acts as the target for all others
const int targetBottom = itemsToAlign.begin()->second.GetBottom();
commit.StageItems( m_selectionTool->GetSelection(), CHT_MODIFY );
auto targetBottom = selectTarget( itemsToAlign, locked_items, []( const ALIGNMENT_RECT& aVal )
{ return aVal.second.GetBottom(); } );
// Move the selected items
for( auto& i : itemsToAlign )
@ -277,26 +292,22 @@ int ALIGN_DISTRIBUTE_TOOL::AlignLeft( const TOOL_EVENT& aEvent )
int ALIGN_DISTRIBUTE_TOOL::doAlignLeft()
{
SELECTION& selection = m_selectionTool->RequestSelection(
[]( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector )
{ EditToolSelectionFilter( aCollector, EXCLUDE_LOCKED | EXCLUDE_TRANSIENTS ); } );
ALIGNMENT_RECTS itemsToAlign;
ALIGNMENT_RECTS locked_items;
if( selection.Size() <= 1 )
if( !GetSelections( itemsToAlign, locked_items, []( const ALIGNMENT_RECT left, const ALIGNMENT_RECT right)
{ return ( left.second.GetLeft() < right.second.GetLeft() ); } ) )
return 0;
auto itemsToAlign = GetBoundingBoxes( selection );
std::sort( itemsToAlign.begin(), itemsToAlign.end(), SortLeftmostX );
BOARD_COMMIT commit( m_frame );
commit.StageItems( selection, CHT_MODIFY );
// after sorting, the fist item acts as the target for all others
const int targetLeft = itemsToAlign.begin()->second.GetX();
commit.StageItems( m_selectionTool->GetSelection(), CHT_MODIFY );
auto targetLeft = selectTarget( itemsToAlign, locked_items, []( const ALIGNMENT_RECT& aVal )
{ return aVal.second.GetLeft(); } );
// Move the selected items
for( auto& i : itemsToAlign )
{
int difference = targetLeft - i.second.GetX();
int difference = targetLeft - i.second.GetLeft();
BOARD_ITEM* item = i.first;
// Don't move a pad by itself unless editing the footprint
@ -329,21 +340,17 @@ int ALIGN_DISTRIBUTE_TOOL::AlignRight( const TOOL_EVENT& aEvent )
int ALIGN_DISTRIBUTE_TOOL::doAlignRight()
{
SELECTION& selection = m_selectionTool->RequestSelection(
[]( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector )
{ EditToolSelectionFilter( aCollector, EXCLUDE_LOCKED | EXCLUDE_TRANSIENTS ); } );
ALIGNMENT_RECTS itemsToAlign;
ALIGNMENT_RECTS locked_items;
if( selection.Size() <= 1 )
if( !GetSelections( itemsToAlign, locked_items, []( const ALIGNMENT_RECT left, const ALIGNMENT_RECT right)
{ return ( left.second.GetRight() < right.second.GetRight() ); } ) )
return 0;
auto itemsToAlign = GetBoundingBoxes( selection );
std::sort( itemsToAlign.begin(), itemsToAlign.end(), SortRightmostX );
BOARD_COMMIT commit( m_frame );
commit.StageItems( selection, CHT_MODIFY );
// after sorting, the fist item acts as the target for all others
const int targetRight = itemsToAlign.begin()->second.GetRight();
commit.StageItems( m_selectionTool->GetSelection(), CHT_MODIFY );
auto targetRight = selectTarget( itemsToAlign, locked_items, []( const ALIGNMENT_RECT& aVal )
{ return aVal.second.GetRight(); } );
// Move the selected items
for( auto& i : itemsToAlign )
@ -366,22 +373,17 @@ int ALIGN_DISTRIBUTE_TOOL::doAlignRight()
int ALIGN_DISTRIBUTE_TOOL::AlignCenterX( const TOOL_EVENT& aEvent )
{
SELECTION& selection = m_selectionTool->RequestSelection(
[]( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector )
{ EditToolSelectionFilter( aCollector, EXCLUDE_LOCKED | EXCLUDE_TRANSIENTS ); } );
ALIGNMENT_RECTS itemsToAlign;
ALIGNMENT_RECTS locked_items;
if( selection.Size() <= 1 )
if( !GetSelections( itemsToAlign, locked_items, []( const ALIGNMENT_RECT left, const ALIGNMENT_RECT right)
{ return ( left.second.GetCenter().x < right.second.GetCenter().x ); } ) )
return 0;
auto itemsToAlign = GetBoundingBoxes( selection );
std::sort( itemsToAlign.begin(), itemsToAlign.end(), SortCenterX );
BOARD_COMMIT commit( m_frame );
commit.StageItems( selection, CHT_MODIFY );
// after sorting use the center x coordinate of the leftmost item as a target
// for all other items
const int targetX = itemsToAlign.begin()->second.GetCenter().x;
commit.StageItems( m_selectionTool->GetSelection(), CHT_MODIFY );
auto targetX = selectTarget( itemsToAlign, locked_items, []( const ALIGNMENT_RECT& aVal )
{ return aVal.second.GetCenter().x; } );
// Move the selected items
for( auto& i : itemsToAlign )
@ -404,22 +406,17 @@ int ALIGN_DISTRIBUTE_TOOL::AlignCenterX( const TOOL_EVENT& aEvent )
int ALIGN_DISTRIBUTE_TOOL::AlignCenterY( const TOOL_EVENT& aEvent )
{
SELECTION& selection = m_selectionTool->RequestSelection(
[]( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector )
{ EditToolSelectionFilter( aCollector, EXCLUDE_LOCKED | EXCLUDE_TRANSIENTS ); } );
ALIGNMENT_RECTS itemsToAlign;
ALIGNMENT_RECTS locked_items;
if( selection.Size() <= 1 )
if( !GetSelections( itemsToAlign, locked_items, []( const ALIGNMENT_RECT left, const ALIGNMENT_RECT right)
{ return ( left.second.GetCenter().y < right.second.GetCenter().y ); } ) )
return 0;
auto itemsToAlign = GetBoundingBoxes( selection );
std::sort( itemsToAlign.begin(), itemsToAlign.end(), SortCenterY );
BOARD_COMMIT commit( m_frame );
commit.StageItems( selection, CHT_MODIFY );
// after sorting use the center y coordinate of the top-most item as a target
// for all other items
const int targetY = itemsToAlign.begin()->second.GetCenter().y;
commit.StageItems( m_selectionTool->GetSelection(), CHT_MODIFY );
auto targetY = selectTarget( itemsToAlign, locked_items, []( const ALIGNMENT_RECT& aVal )
{ return aVal.second.GetCenter().y; } );
// Move the selected items
for( auto& i : itemsToAlign )
@ -455,13 +452,17 @@ int ALIGN_DISTRIBUTE_TOOL::DistributeHorizontally( const TOOL_EVENT& aEvent )
auto itemsToDistribute = GetBoundingBoxes( selection );
// find the last item by reverse sorting
std::sort( itemsToDistribute.begin(), itemsToDistribute.end(), SortRightmostX );
std::sort( itemsToDistribute.begin(), itemsToDistribute.end(),
[] ( const ALIGNMENT_RECT left, const ALIGNMENT_RECT right)
{ return ( left.second.GetRight() > right.second.GetRight() ); } );
const auto lastItem = itemsToDistribute.begin()->first;
const auto maxRight = itemsToDistribute.begin()->second.GetRight();
// sort to get starting order
std::sort( itemsToDistribute.begin(), itemsToDistribute.end(), SortLeftmostX );
std::sort( itemsToDistribute.begin(), itemsToDistribute.end(),
[] ( const ALIGNMENT_RECT left, const ALIGNMENT_RECT right)
{ return ( left.second.GetX() < right.second.GetX() ); } );
const auto minX = itemsToDistribute.begin()->second.GetX();
auto totalGap = maxRight - minX;
int totalWidth = 0;
@ -515,7 +516,9 @@ void ALIGN_DISTRIBUTE_TOOL::doDistributeGapsHorizontally( ALIGNMENT_RECTS& items
void ALIGN_DISTRIBUTE_TOOL::doDistributeCentersHorizontally( ALIGNMENT_RECTS &itemsToDistribute ) const
{
std::sort( itemsToDistribute.begin(), itemsToDistribute.end(), SortCenterX );
std::sort( itemsToDistribute.begin(), itemsToDistribute.end(),
[] ( const ALIGNMENT_RECT left, const ALIGNMENT_RECT right)
{ return ( left.second.GetCenter().x < right.second.GetCenter().x ); } );
const auto totalGap = ( itemsToDistribute.end()-1 )->second.GetCenter().x
- itemsToDistribute.begin()->second.GetCenter().x;
const auto itemGap = totalGap / ( itemsToDistribute.size() - 1 );
@ -551,12 +554,16 @@ int ALIGN_DISTRIBUTE_TOOL::DistributeVertically( const TOOL_EVENT& aEvent )
auto itemsToDistribute = GetBoundingBoxes( selection );
// find the last item by reverse sorting
std::sort( itemsToDistribute.begin(), itemsToDistribute.end(), SortBottommostY );
std::sort( itemsToDistribute.begin(), itemsToDistribute.end(),
[] ( const ALIGNMENT_RECT left, const ALIGNMENT_RECT right)
{ return ( left.second.GetBottom() > right.second.GetBottom() ); } );
const auto maxBottom = itemsToDistribute.begin()->second.GetBottom();
const auto lastItem = itemsToDistribute.begin()->first;
// sort to get starting order
std::sort( itemsToDistribute.begin(), itemsToDistribute.end(), SortTopmostY );
std::sort( itemsToDistribute.begin(), itemsToDistribute.end(),
[] ( const ALIGNMENT_RECT left, const ALIGNMENT_RECT right)
{ return ( left.second.GetCenter().y < right.second.GetCenter().y ); } );
auto minY = itemsToDistribute.begin()->second.GetY();
auto totalGap = maxBottom - minY;
@ -611,7 +618,9 @@ void ALIGN_DISTRIBUTE_TOOL::doDistributeGapsVertically( ALIGNMENT_RECTS& itemsTo
void ALIGN_DISTRIBUTE_TOOL::doDistributeCentersVertically( ALIGNMENT_RECTS& itemsToDistribute ) const
{
std::sort( itemsToDistribute.begin(), itemsToDistribute.end(), SortCenterY );
std::sort( itemsToDistribute.begin(), itemsToDistribute.end(),
[] ( const ALIGNMENT_RECT left, const ALIGNMENT_RECT right)
{ return ( left.second.GetCenter().y < right.second.GetCenter().y ); } );
const auto totalGap = ( itemsToDistribute.end()-1 )->second.GetCenter().y
- itemsToDistribute.begin()->second.GetCenter().y;
const auto itemGap = totalGap / ( itemsToDistribute.size() - 1 );

View File

@ -29,7 +29,8 @@
#include <class_board_item.h>
#include <pcb_base_frame.h>
typedef std::vector<std::pair<BOARD_ITEM*, EDA_RECT>> ALIGNMENT_RECTS;
using ALIGNMENT_RECT = std::pair<BOARD_ITEM*, EDA_RECT>;
using ALIGNMENT_RECTS = std::vector<ALIGNMENT_RECT>;
class SELECTION_TOOL;
@ -100,6 +101,17 @@ public:
private:
/**
* Function GetSelections()
* Populates two vectors with the sorted selection and sorted locked items
* Returns the size of aItems()
*/
template< typename T >
size_t GetSelections( ALIGNMENT_RECTS& aItems, ALIGNMENT_RECTS& aLocked, T aCompare );
template< typename T >
int selectTarget( ALIGNMENT_RECTS& aItems, ALIGNMENT_RECTS& aLocked, T aGetValue );
/**
* Sets X coordinate of the selected items to the value of the left-most selected item X coordinate.
*