mirror of
https://gitlab.com/kicad/code/kicad.git
synced 2025-09-14 02:03:12 +02:00
Always look for pre-existing undo/redo record. Checking for IsNew() is less robust and should be avoided. Also moves the checking to a location where it will be easier to ensure that it's uniform. Push get-undo-level-item processing down a level so it is uniformly called. Make sure tables & labels are uniformly handled. Remove incorrect usage of Get/SetGroupId() for storing lastPin (which we don't use anyway). Lists of deleted and changed items MUST include the screen pointer. An item could be changed on one screen but not on another. Also tightens handling of PCB_NETINFO_T items, which are not in the view. Also fixes a bug where there is no increment parameter if you assign the base increment command to a hotkey. (This was discovered while testing the above changes.) Also fixes a bug where delete during a move in PCB Editor did an undo instead of a delete. (Again, found while testing above.) An experiment was also run to collapse shared parts of SCH_EDIT_FRAME and SYMBOL_EDITOR_FRAME into SCH_BASE_EDIT_FRAME. However, sharing the undo code actually increased complexity, and there was very little else of value in SCH_BASE_EDIT_FRAME (other than the Increment() routines).
855 lines
31 KiB
C++
855 lines
31 KiB
C++
/*
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
*
|
|
* Copyright (C) 2013-2017 CERN
|
|
* Copyright The KiCad Developers, see AUTHORS.txt for contributors.
|
|
* @author Maciej Suminski <maciej.suminski@cern.ch>
|
|
* @author Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, you may find one here:
|
|
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
|
|
* or you may search the http://www.gnu.org website for the version 2 license,
|
|
* or you may write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
|
*/
|
|
|
|
#include <functional>
|
|
#include <limits>
|
|
#include <kiplatform/ui.h>
|
|
#include <board.h>
|
|
#include <board_commit.h>
|
|
#include <gal/graphics_abstraction_layer.h>
|
|
#include <geometry/geometry_utils.h>
|
|
#include <pad.h>
|
|
#include <pcb_group.h>
|
|
#include <pcb_generator.h>
|
|
#include <pcb_edit_frame.h>
|
|
#include <spread_footprints.h>
|
|
#include <tool/tool_manager.h>
|
|
#include <tools/pcb_actions.h>
|
|
#include <tools/pcb_selection_tool.h>
|
|
#include <tools/edit_tool.h>
|
|
#include <tools/pcb_grid_helper.h>
|
|
#include <tools/drc_tool.h>
|
|
#include <tools/zone_filler_tool.h>
|
|
#include <router/router_tool.h>
|
|
#include <dialogs/dialog_move_exact.h>
|
|
#include <zone_filler.h>
|
|
#include <drc/drc_engine.h>
|
|
#include <drc/drc_interactive_courtyard_clearance.h>
|
|
#include <view/view_controls.h>
|
|
|
|
|
|
int EDIT_TOOL::Swap( const TOOL_EVENT& aEvent )
|
|
{
|
|
if( isRouterActive() )
|
|
{
|
|
wxBell();
|
|
return 0;
|
|
}
|
|
|
|
PCB_SELECTION& selection = m_selectionTool->RequestSelection(
|
|
[]( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
|
|
{
|
|
sTool->FilterCollectorForMarkers( aCollector );
|
|
sTool->FilterCollectorForHierarchy( aCollector, true );
|
|
sTool->FilterCollectorForFreePads( aCollector );
|
|
|
|
// Iterate from the back so we don't have to worry about removals.
|
|
for( int i = aCollector.GetCount() - 1; i >= 0; --i )
|
|
{
|
|
BOARD_ITEM* item = aCollector[i];
|
|
|
|
if( item->Type() == PCB_TRACE_T )
|
|
aCollector.Remove( item );
|
|
}
|
|
},
|
|
true /* prompt user regarding locked items */ );
|
|
|
|
if( selection.Size() < 2 )
|
|
return 0;
|
|
|
|
BOARD_COMMIT localCommit( this );
|
|
BOARD_COMMIT* commit = dynamic_cast<BOARD_COMMIT*>( aEvent.Commit() );
|
|
|
|
if( !commit )
|
|
commit = &localCommit;
|
|
|
|
std::vector<EDA_ITEM*> sorted = selection.GetItemsSortedBySelectionOrder();
|
|
|
|
// Save items, so changes can be undone
|
|
for( EDA_ITEM* item : selection )
|
|
commit->Modify( item, nullptr, RECURSE_MODE::RECURSE );
|
|
|
|
for( size_t i = 0; i < sorted.size() - 1; i++ )
|
|
{
|
|
EDA_ITEM* edaItemA = sorted[i];
|
|
EDA_ITEM* edaItemB = sorted[( i + 1 ) % sorted.size()];
|
|
|
|
if( !edaItemA->IsBOARD_ITEM() || !edaItemB->IsBOARD_ITEM() )
|
|
continue;
|
|
|
|
BOARD_ITEM* a = static_cast<BOARD_ITEM*>( edaItemA );
|
|
BOARD_ITEM* b = static_cast<BOARD_ITEM*>( edaItemB );
|
|
|
|
// Swap X,Y position
|
|
VECTOR2I aPos = a->GetPosition(), bPos = b->GetPosition();
|
|
std::swap( aPos, bPos );
|
|
a->SetPosition( aPos );
|
|
b->SetPosition( bPos );
|
|
|
|
// Handle footprints specially. They can be flipped to the back of the board which
|
|
// requires a special transformation.
|
|
if( a->Type() == PCB_FOOTPRINT_T && b->Type() == PCB_FOOTPRINT_T )
|
|
{
|
|
FOOTPRINT* aFP = static_cast<FOOTPRINT*>( a );
|
|
FOOTPRINT* bFP = static_cast<FOOTPRINT*>( b );
|
|
|
|
// Store initial orientation of footprints, before flipping them.
|
|
EDA_ANGLE aAngle = aFP->GetOrientation();
|
|
EDA_ANGLE bAngle = bFP->GetOrientation();
|
|
|
|
// Flip both if needed
|
|
if( aFP->IsFlipped() != bFP->IsFlipped() )
|
|
{
|
|
aFP->Flip( aPos, FLIP_DIRECTION::TOP_BOTTOM );
|
|
bFP->Flip( bPos, FLIP_DIRECTION::TOP_BOTTOM );
|
|
}
|
|
|
|
// Set orientation
|
|
std::swap( aAngle, bAngle );
|
|
aFP->SetOrientation( aAngle );
|
|
bFP->SetOrientation( bAngle );
|
|
}
|
|
// We can also do a layer swap safely for two objects of the same type,
|
|
// except groups which don't support layer swaps.
|
|
else if( a->Type() == b->Type() && a->Type() != PCB_GROUP_T )
|
|
{
|
|
// Swap layers
|
|
PCB_LAYER_ID aLayer = a->GetLayer(), bLayer = b->GetLayer();
|
|
std::swap( aLayer, bLayer );
|
|
a->SetLayer( aLayer );
|
|
b->SetLayer( bLayer );
|
|
}
|
|
}
|
|
|
|
if( !localCommit.Empty() )
|
|
localCommit.Push( _( "Swap" ) );
|
|
|
|
m_toolMgr->ProcessEvent( EVENTS::SelectedItemsModified );
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int EDIT_TOOL::PackAndMoveFootprints( const TOOL_EVENT& aEvent )
|
|
{
|
|
if( isRouterActive() || m_dragging )
|
|
{
|
|
wxBell();
|
|
return 0;
|
|
}
|
|
|
|
BOARD_COMMIT commit( this );
|
|
PCB_SELECTION& selection = m_selectionTool->RequestSelection(
|
|
[]( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
|
|
{
|
|
sTool->FilterCollectorForMarkers( aCollector );
|
|
sTool->FilterCollectorForHierarchy( aCollector, true );
|
|
sTool->FilterCollectorForFreePads( aCollector, true );
|
|
|
|
// Iterate from the back so we don't have to worry about removals.
|
|
for( int i = aCollector.GetCount() - 1; i >= 0; --i )
|
|
{
|
|
BOARD_ITEM* item = aCollector[i];
|
|
|
|
if( !dynamic_cast<FOOTPRINT*>( item ) )
|
|
aCollector.Remove( item );
|
|
}
|
|
},
|
|
true /* prompt user regarding locked items */ );
|
|
|
|
std::vector<FOOTPRINT*> footprintsToPack;
|
|
|
|
for( EDA_ITEM* item : selection )
|
|
footprintsToPack.push_back( static_cast<FOOTPRINT*>( item ) );
|
|
|
|
if( footprintsToPack.empty() )
|
|
return 0;
|
|
|
|
BOX2I footprintsBbox;
|
|
|
|
for( FOOTPRINT* fp : footprintsToPack )
|
|
{
|
|
commit.Modify( fp );
|
|
fp->SetFlags( IS_MOVING );
|
|
footprintsBbox.Merge( fp->GetBoundingBox( false ) );
|
|
}
|
|
|
|
SpreadFootprints( &footprintsToPack, footprintsBbox.Normalize().GetOrigin(), false );
|
|
|
|
if( doMoveSelection( aEvent, &commit, true ) )
|
|
commit.Push( _( "Pack Footprints" ) );
|
|
else
|
|
commit.Revert();
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int EDIT_TOOL::Move( const TOOL_EVENT& aEvent )
|
|
{
|
|
if( isRouterActive() || m_dragging )
|
|
{
|
|
wxBell();
|
|
return 0;
|
|
}
|
|
|
|
if( BOARD_COMMIT* commit = dynamic_cast<BOARD_COMMIT*>( aEvent.Commit() ) )
|
|
{
|
|
// Most moves will be synchronous unless they are coming from the API
|
|
if( aEvent.SynchronousState() )
|
|
aEvent.SynchronousState()->store( STS_RUNNING );
|
|
|
|
if( doMoveSelection( aEvent, commit, true ) )
|
|
{
|
|
if( aEvent.SynchronousState() )
|
|
aEvent.SynchronousState()->store( STS_FINISHED );
|
|
}
|
|
else if( aEvent.SynchronousState() )
|
|
{
|
|
aEvent.SynchronousState()->store( STS_CANCELLED );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
BOARD_COMMIT localCommit( this );
|
|
|
|
if( doMoveSelection( aEvent, &localCommit, false ) )
|
|
localCommit.Push( _( "Move" ) );
|
|
else
|
|
localCommit.Revert();
|
|
}
|
|
|
|
// Notify point editor. (While doMoveSelection() will re-select the items and post this
|
|
// event, it's done before the edit flags are cleared in BOARD_COMMIT::Push() so the point
|
|
// editor doesn't fire up.)
|
|
m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
VECTOR2I EDIT_TOOL::getSafeMovement( const VECTOR2I& aMovement, const BOX2I& aSourceBBox,
|
|
const VECTOR2D& aBBoxOffset )
|
|
{
|
|
typedef std::numeric_limits<int> coord_limits;
|
|
|
|
static const double max = coord_limits::max() - (int) COORDS_PADDING;
|
|
static const double min = -max;
|
|
|
|
BOX2D testBox( aSourceBBox.GetPosition(), aSourceBBox.GetSize() );
|
|
testBox.Offset( aBBoxOffset );
|
|
|
|
// Do not restrict movement if bounding box is already out of bounds
|
|
if( testBox.GetLeft() < min || testBox.GetTop() < min || testBox.GetRight() > max
|
|
|| testBox.GetBottom() > max )
|
|
{
|
|
return aMovement;
|
|
}
|
|
|
|
testBox.Offset( aMovement );
|
|
|
|
if( testBox.GetLeft() < min )
|
|
testBox.Offset( min - testBox.GetLeft(), 0 );
|
|
|
|
if( max < testBox.GetRight() )
|
|
testBox.Offset( -( testBox.GetRight() - max ), 0 );
|
|
|
|
if( testBox.GetTop() < min )
|
|
testBox.Offset( 0, min - testBox.GetTop() );
|
|
|
|
if( max < testBox.GetBottom() )
|
|
testBox.Offset( 0, -( testBox.GetBottom() - max ) );
|
|
|
|
return KiROUND( testBox.GetPosition() - aBBoxOffset - aSourceBBox.GetPosition() );
|
|
}
|
|
|
|
|
|
bool EDIT_TOOL::doMoveSelection( const TOOL_EVENT& aEvent, BOARD_COMMIT* aCommit, bool aAutoStart )
|
|
{
|
|
const bool moveWithReference = aEvent.IsAction( &PCB_ACTIONS::moveWithReference );
|
|
const bool moveIndividually = aEvent.IsAction( &PCB_ACTIONS::moveIndividually );
|
|
|
|
PCB_BASE_EDIT_FRAME* editFrame = getEditFrame<PCB_BASE_EDIT_FRAME>();
|
|
PCBNEW_SETTINGS* cfg = editFrame->GetPcbNewSettings();
|
|
BOARD* board = editFrame->GetBoard();
|
|
KIGFX::VIEW_CONTROLS* controls = getViewControls();
|
|
VECTOR2I originalCursorPos = controls->GetCursorPosition();
|
|
VECTOR2I originalMousePos = controls->GetMousePosition();
|
|
std::unique_ptr<STATUS_TEXT_POPUP> statusPopup;
|
|
wxString status;
|
|
size_t itemIdx = 0;
|
|
|
|
// Be sure that there is at least one item that we can modify. If nothing was selected before,
|
|
// try looking for the stuff under mouse cursor (i.e. KiCad old-style hover selection)
|
|
PCB_SELECTION& selection = m_selectionTool->RequestSelection(
|
|
[]( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
|
|
{
|
|
sTool->FilterCollectorForMarkers( aCollector );
|
|
sTool->FilterCollectorForHierarchy( aCollector, true );
|
|
sTool->FilterCollectorForFreePads( aCollector );
|
|
sTool->FilterCollectorForTableCells( aCollector );
|
|
},
|
|
true /* prompt user regarding locked items */ );
|
|
|
|
if( m_dragging || selection.Empty() )
|
|
return false;
|
|
|
|
TOOL_EVENT pushedEvent = aEvent;
|
|
editFrame->PushTool( aEvent );
|
|
Activate();
|
|
|
|
// Must be done after Activate() so that it gets set into the correct context
|
|
controls->ShowCursor( true );
|
|
controls->SetAutoPan( true );
|
|
controls->ForceCursorPosition( false );
|
|
|
|
auto displayConstraintsMessage =
|
|
[editFrame]( bool constrained )
|
|
{
|
|
editFrame->DisplayConstraintsMsg( constrained ? _( "Constrain to H, V, 45" )
|
|
: wxString( wxT( "" ) ) );
|
|
};
|
|
|
|
auto updateStatusPopup =
|
|
[&]( EDA_ITEM* item, size_t ii, size_t count )
|
|
{
|
|
wxString popuptext = _( "Click to place %s (item %zu of %zu)\n"
|
|
"Press <esc> to cancel all; double-click to finish" );
|
|
wxString msg;
|
|
|
|
if( item->Type() == PCB_FOOTPRINT_T )
|
|
{
|
|
FOOTPRINT* fp = static_cast<FOOTPRINT*>( item );
|
|
msg = fp->GetReference();
|
|
}
|
|
else if( item->Type() == PCB_PAD_T )
|
|
{
|
|
PAD* pad = static_cast<PAD*>( item );
|
|
FOOTPRINT* fp = pad->GetParentFootprint();
|
|
msg = wxString::Format( _( "%s pad %s" ), fp->GetReference(), pad->GetNumber() );
|
|
}
|
|
else
|
|
{
|
|
msg = item->GetTypeDesc().Lower();
|
|
}
|
|
|
|
if( !statusPopup )
|
|
statusPopup = std::make_unique<STATUS_TEXT_POPUP>( frame() );
|
|
|
|
statusPopup->SetText( wxString::Format( popuptext, msg, ii, count ) );
|
|
};
|
|
|
|
std::vector<BOARD_ITEM*> sel_items; // All the items operated on by the move below
|
|
std::vector<BOARD_ITEM*> orig_items; // All the original items in the selection
|
|
|
|
for( EDA_ITEM* item : selection )
|
|
{
|
|
if( item->IsBOARD_ITEM() )
|
|
{
|
|
BOARD_ITEM* boardItem = static_cast<BOARD_ITEM*>( item );
|
|
|
|
if( !selection.IsHover() )
|
|
orig_items.push_back( boardItem );
|
|
|
|
sel_items.push_back( boardItem );
|
|
}
|
|
|
|
if( item->Type() == PCB_FOOTPRINT_T )
|
|
{
|
|
FOOTPRINT* footprint = static_cast<FOOTPRINT*>( item );
|
|
|
|
for( PAD* pad : footprint->Pads() )
|
|
sel_items.push_back( pad );
|
|
|
|
// Clear this flag here; it will be set by the netlist updater if the footprint is new
|
|
// so that it was skipped in the initial connectivity update in OnNetlistChanged
|
|
footprint->SetAttributes( footprint->GetAttributes() & ~FP_JUST_ADDED );
|
|
}
|
|
}
|
|
|
|
VECTOR2I pickedReferencePoint;
|
|
|
|
if( moveWithReference && !pickReferencePoint( _( "Select reference point for move..." ), "", "",
|
|
pickedReferencePoint ) )
|
|
{
|
|
if( selection.IsHover() )
|
|
m_toolMgr->RunAction( ACTIONS::selectionClear );
|
|
|
|
editFrame->PopTool( pushedEvent );
|
|
return false;
|
|
}
|
|
|
|
if( moveIndividually )
|
|
{
|
|
orig_items.clear();
|
|
|
|
for( EDA_ITEM* item : selection.GetItemsSortedBySelectionOrder() )
|
|
{
|
|
if( item->IsBOARD_ITEM() )
|
|
orig_items.push_back( static_cast<BOARD_ITEM*>( item ) );
|
|
}
|
|
|
|
updateStatusPopup( orig_items[ itemIdx ], itemIdx + 1, orig_items.size() );
|
|
statusPopup->Popup();
|
|
statusPopup->Move( KIPLATFORM::UI::GetMousePosition() + wxPoint( 20, 20 ) );
|
|
canvas()->SetStatusPopup( statusPopup->GetPanel() );
|
|
|
|
m_selectionTool->ClearSelection();
|
|
m_selectionTool->AddItemToSel( orig_items[ itemIdx ] );
|
|
|
|
sel_items.clear();
|
|
sel_items.push_back( orig_items[ itemIdx ] );
|
|
}
|
|
|
|
bool restore_state = false;
|
|
VECTOR2I originalPos;
|
|
VECTOR2D bboxMovement;
|
|
BOX2I originalBBox;
|
|
bool updateBBox = true;
|
|
LSET layers( { editFrame->GetActiveLayer() } );
|
|
PCB_GRID_HELPER grid( m_toolMgr, editFrame->GetMagneticItemsSettings() );
|
|
TOOL_EVENT copy = aEvent;
|
|
TOOL_EVENT* evt = ©
|
|
VECTOR2I prevPos;
|
|
bool enableLocalRatsnest = true;
|
|
|
|
bool hv45Mode = GetAngleSnapMode() != LEADER_MODE::DIRECT;
|
|
bool eatFirstMouseUp = true;
|
|
bool allowRedraw3D = cfg->m_Display.m_Live3DRefresh;
|
|
bool showCourtyardConflicts = !m_isFootprintEditor && cfg->m_ShowCourtyardCollisions;
|
|
|
|
// Used to test courtyard overlaps
|
|
std::unique_ptr<DRC_INTERACTIVE_COURTYARD_CLEARANCE> drc_on_move = nullptr;
|
|
|
|
if( showCourtyardConflicts )
|
|
{
|
|
std::shared_ptr<DRC_ENGINE> drcEngine = m_toolMgr->GetTool<DRC_TOOL>()->GetDRCEngine();
|
|
drc_on_move.reset( new DRC_INTERACTIVE_COURTYARD_CLEARANCE( drcEngine ) );
|
|
drc_on_move->Init( board );
|
|
}
|
|
|
|
displayConstraintsMessage( hv45Mode );
|
|
|
|
// Prime the pump
|
|
m_toolMgr->PostAction( ACTIONS::refreshPreview );
|
|
|
|
// Main loop: keep receiving events
|
|
do
|
|
{
|
|
VECTOR2I movement;
|
|
editFrame->GetCanvas()->SetCurrentCursor( KICURSOR::MOVING );
|
|
grid.SetSnap( !evt->Modifier( MD_SHIFT ) );
|
|
grid.SetUseGrid( getView()->GetGAL()->GetGridSnapping() && !evt->DisableGridSnapping() );
|
|
|
|
bool isSkip = evt->IsAction( &PCB_ACTIONS::skip ) && moveIndividually;
|
|
|
|
if( evt->IsMotion() || evt->IsDrag( BUT_LEFT ) )
|
|
eatFirstMouseUp = false;
|
|
|
|
if( evt->IsAction( &PCB_ACTIONS::move )
|
|
|| evt->IsMotion()
|
|
|| evt->IsDrag( BUT_LEFT )
|
|
|| evt->IsAction( &ACTIONS::refreshPreview )
|
|
|| evt->IsAction( &PCB_ACTIONS::moveWithReference )
|
|
|| evt->IsAction( &PCB_ACTIONS::moveIndividually ) )
|
|
{
|
|
if( m_dragging && ( evt->IsMotion()
|
|
|| evt->IsDrag( BUT_LEFT )
|
|
|| evt->IsAction( &ACTIONS::refreshPreview ) ) )
|
|
{
|
|
bool redraw3D = false;
|
|
|
|
VECTOR2I mousePos( controls->GetMousePosition() );
|
|
|
|
m_cursor = grid.BestSnapAnchor( mousePos, layers, grid.GetSelectionGrid( selection ), sel_items );
|
|
|
|
if( controls->GetSettings().m_lastKeyboardCursorPositionValid )
|
|
{
|
|
grid.SetSnap( false );
|
|
grid.SetUseGrid( false );
|
|
}
|
|
|
|
m_cursor = grid.BestSnapAnchor( mousePos, layers, grid.GetSelectionGrid( selection ), sel_items );
|
|
|
|
if( !selection.HasReferencePoint() )
|
|
originalPos = m_cursor;
|
|
|
|
if( hv45Mode )
|
|
{
|
|
VECTOR2I moveVector = m_cursor - originalPos;
|
|
m_cursor = originalPos + GetVectorSnapped45( moveVector );
|
|
}
|
|
|
|
if( updateBBox )
|
|
{
|
|
originalBBox = BOX2I();
|
|
bboxMovement = VECTOR2D();
|
|
|
|
for( EDA_ITEM* item : sel_items )
|
|
originalBBox.Merge( item->ViewBBox() );
|
|
|
|
updateBBox = false;
|
|
}
|
|
|
|
// Constrain selection bounding box to coordinates limits
|
|
movement = getSafeMovement( m_cursor - prevPos, originalBBox, bboxMovement );
|
|
|
|
// Apply constrained movement
|
|
m_cursor = prevPos + movement;
|
|
|
|
controls->ForceCursorPosition( true, m_cursor );
|
|
selection.SetReferencePoint( m_cursor );
|
|
|
|
prevPos = m_cursor;
|
|
bboxMovement += movement;
|
|
|
|
// Drag items to the current cursor position
|
|
for( BOARD_ITEM* item : sel_items )
|
|
{
|
|
// Don't double move child items.
|
|
if( !item->GetParent() || !item->GetParent()->IsSelected() )
|
|
item->Move( movement );
|
|
|
|
if( item->Type() == PCB_GENERATOR_T && sel_items.size() == 1 )
|
|
{
|
|
m_toolMgr->RunSynchronousAction( PCB_ACTIONS::genUpdateEdit, aCommit,
|
|
static_cast<PCB_GENERATOR*>( item ) );
|
|
}
|
|
|
|
if( item->Type() == PCB_FOOTPRINT_T )
|
|
redraw3D = true;
|
|
}
|
|
|
|
if( redraw3D && allowRedraw3D )
|
|
editFrame->Update3DView( false, true );
|
|
|
|
if( showCourtyardConflicts && drc_on_move->m_FpInMove.size() )
|
|
{
|
|
drc_on_move->Run();
|
|
drc_on_move->UpdateConflicts( m_toolMgr->GetView(), true );
|
|
}
|
|
|
|
m_toolMgr->PostEvent( EVENTS::SelectedItemsMoved );
|
|
}
|
|
else if( !m_dragging && ( aAutoStart || !evt->IsAction( &ACTIONS::refreshPreview ) ) )
|
|
{
|
|
// Prepare to start dragging
|
|
editFrame->HideSolderMask();
|
|
|
|
m_dragging = true;
|
|
|
|
for( BOARD_ITEM* item : sel_items )
|
|
{
|
|
if( item->GetParent() && item->GetParent()->IsSelected() )
|
|
continue;
|
|
|
|
if( !item->IsNew() && !item->IsMoving() )
|
|
{
|
|
if( item->Type() == PCB_GENERATOR_T && sel_items.size() == 1 )
|
|
{
|
|
enableLocalRatsnest = false;
|
|
|
|
m_toolMgr->RunSynchronousAction( PCB_ACTIONS::genStartEdit, aCommit,
|
|
static_cast<PCB_GENERATOR*>( item ) );
|
|
}
|
|
else
|
|
{
|
|
aCommit->Modify( item, nullptr, RECURSE_MODE::RECURSE );
|
|
}
|
|
|
|
item->SetFlags( IS_MOVING );
|
|
|
|
if( item->Type() == PCB_SHAPE_T )
|
|
static_cast<PCB_SHAPE*>( item )->UpdateHatching();
|
|
|
|
item->RunOnChildren(
|
|
[&]( BOARD_ITEM* child )
|
|
{
|
|
child->SetFlags( IS_MOVING );
|
|
|
|
if( child->Type() == PCB_SHAPE_T )
|
|
static_cast<PCB_SHAPE*>( child )->UpdateHatching();
|
|
},
|
|
RECURSE_MODE::RECURSE );
|
|
}
|
|
}
|
|
|
|
m_cursor = controls->GetCursorPosition();
|
|
|
|
if( selection.HasReferencePoint() )
|
|
{
|
|
// start moving with the reference point attached to the cursor
|
|
grid.SetAuxAxes( false );
|
|
|
|
if( hv45Mode )
|
|
{
|
|
VECTOR2I moveVector = m_cursor - originalPos;
|
|
m_cursor = originalPos + GetVectorSnapped45( moveVector );
|
|
}
|
|
|
|
movement = m_cursor - selection.GetReferencePoint();
|
|
|
|
// Drag items to the current cursor position
|
|
for( EDA_ITEM* item : selection )
|
|
{
|
|
if( !item->IsBOARD_ITEM() )
|
|
continue;
|
|
|
|
// Don't double move footprint pads, fields, etc.
|
|
if( item->GetParent() && item->GetParent()->IsSelected() )
|
|
continue;
|
|
|
|
static_cast<BOARD_ITEM*>( item )->Move( movement );
|
|
}
|
|
|
|
selection.SetReferencePoint( m_cursor );
|
|
}
|
|
else
|
|
{
|
|
if( showCourtyardConflicts )
|
|
{
|
|
std::vector<FOOTPRINT*>& FPs = drc_on_move->m_FpInMove;
|
|
|
|
for( BOARD_ITEM* item : sel_items )
|
|
{
|
|
if( item->Type() == PCB_FOOTPRINT_T )
|
|
FPs.push_back( static_cast<FOOTPRINT*>( item ) );
|
|
|
|
item->RunOnChildren(
|
|
[&]( BOARD_ITEM* child )
|
|
{
|
|
if( child->Type() == PCB_FOOTPRINT_T )
|
|
FPs.push_back( static_cast<FOOTPRINT*>( child ) );
|
|
},
|
|
RECURSE_MODE::RECURSE );
|
|
}
|
|
}
|
|
|
|
// Use the mouse position over cursor, as otherwise large grids will allow only
|
|
// snapping to items that are closest to grid points
|
|
m_cursor = grid.BestDragOrigin( originalMousePos, sel_items, grid.GetSelectionGrid( selection ),
|
|
&m_selectionTool->GetFilter() );
|
|
|
|
// Set the current cursor position to the first dragged item origin, so the
|
|
// movement vector could be computed later
|
|
if( moveWithReference )
|
|
{
|
|
selection.SetReferencePoint( pickedReferencePoint );
|
|
controls->ForceCursorPosition( true, pickedReferencePoint );
|
|
m_cursor = pickedReferencePoint;
|
|
}
|
|
else
|
|
{
|
|
// Check if user wants to warp the mouse to origin of moved object
|
|
if( !editFrame->GetMoveWarpsCursor() )
|
|
m_cursor = originalCursorPos; // No, so use original mouse pos instead
|
|
|
|
selection.SetReferencePoint( m_cursor );
|
|
grid.SetAuxAxes( true, m_cursor );
|
|
}
|
|
|
|
originalPos = m_cursor;
|
|
}
|
|
|
|
// Update variables for bounding box collision calculations
|
|
updateBBox = true;
|
|
|
|
controls->SetCursorPosition( m_cursor, false );
|
|
|
|
prevPos = m_cursor;
|
|
controls->SetAutoPan( true );
|
|
m_toolMgr->PostEvent( EVENTS::SelectedItemsModified );
|
|
}
|
|
|
|
if( statusPopup )
|
|
statusPopup->Move( KIPLATFORM::UI::GetMousePosition() + wxPoint( 20, 20 ) );
|
|
|
|
if( enableLocalRatsnest )
|
|
m_toolMgr->PostAction( PCB_ACTIONS::updateLocalRatsnest, movement );
|
|
}
|
|
else if( evt->IsCancelInteractive() || evt->IsActivate() )
|
|
{
|
|
if( m_dragging && evt->IsCancelInteractive() )
|
|
evt->SetPassEvent( false );
|
|
|
|
restore_state = true; // Canceling the tool means that items have to be restored
|
|
break; // Finish
|
|
}
|
|
else if( evt->IsClick( BUT_RIGHT ) )
|
|
{
|
|
m_selectionTool->GetToolMenu().ShowContextMenu( selection );
|
|
}
|
|
else if( evt->IsAction( &ACTIONS::undo ) )
|
|
{
|
|
restore_state = true; // Perform undo locally
|
|
break; // Finish
|
|
}
|
|
else if( evt->IsAction( &ACTIONS::doDelete ) )
|
|
{
|
|
evt->SetPassEvent();
|
|
// Exit on a delete; there will no longer be anything to drag.
|
|
break;
|
|
}
|
|
else if( evt->IsAction( &ACTIONS::duplicate ) || evt->IsAction( &ACTIONS::cut ) )
|
|
{
|
|
wxBell();
|
|
}
|
|
else if( evt->IsAction( &PCB_ACTIONS::rotateCw )
|
|
|| evt->IsAction( &PCB_ACTIONS::rotateCcw )
|
|
|| evt->IsAction( &PCB_ACTIONS::flip )
|
|
|| evt->IsAction( &PCB_ACTIONS::mirrorH )
|
|
|| evt->IsAction( &PCB_ACTIONS::mirrorV ) )
|
|
{
|
|
updateBBox = true;
|
|
eatFirstMouseUp = false;
|
|
evt->SetPassEvent();
|
|
}
|
|
else if( evt->IsMouseUp( BUT_LEFT ) || evt->IsClick( BUT_LEFT ) || isSkip )
|
|
{
|
|
// Eat mouse-up/-click events that leaked through from the lock dialog
|
|
if( eatFirstMouseUp && !evt->IsAction( &ACTIONS::cursorClick ) )
|
|
{
|
|
eatFirstMouseUp = false;
|
|
continue;
|
|
}
|
|
else if( moveIndividually && m_dragging )
|
|
{
|
|
// Put skipped items back where they started
|
|
if( isSkip )
|
|
orig_items[itemIdx]->SetPosition( originalPos );
|
|
|
|
view()->Update( orig_items[itemIdx] );
|
|
rebuildConnectivity();
|
|
|
|
if( ++itemIdx < orig_items.size() )
|
|
{
|
|
BOARD_ITEM* nextItem = orig_items[itemIdx];
|
|
|
|
m_selectionTool->ClearSelection();
|
|
|
|
originalPos = nextItem->GetPosition();
|
|
m_selectionTool->AddItemToSel( nextItem );
|
|
selection.SetReferencePoint( originalPos );
|
|
|
|
sel_items.clear();
|
|
sel_items.push_back( nextItem );
|
|
updateStatusPopup( nextItem, itemIdx + 1, orig_items.size() );
|
|
|
|
// Pick up new item
|
|
aCommit->Modify( nextItem, nullptr, RECURSE_MODE::RECURSE );
|
|
nextItem->Move( controls->GetCursorPosition( true ) - nextItem->GetPosition() );
|
|
|
|
continue;
|
|
}
|
|
}
|
|
|
|
break; // finish
|
|
}
|
|
else if( evt->IsDblClick( BUT_LEFT ) )
|
|
{
|
|
// The first click will move the new item, so put it back
|
|
if( moveIndividually )
|
|
orig_items[itemIdx]->SetPosition( originalPos );
|
|
|
|
break; // finish
|
|
}
|
|
else if( evt->IsAction( &PCB_ACTIONS::toggleHV45Mode ) )
|
|
{
|
|
hv45Mode = GetAngleSnapMode() != LEADER_MODE::DIRECT;
|
|
displayConstraintsMessage( hv45Mode );
|
|
evt->SetPassEvent( false );
|
|
}
|
|
else if( evt->IsAction( &ACTIONS::increment ) )
|
|
{
|
|
if( evt->HasParameter() )
|
|
m_toolMgr->RunSynchronousAction( ACTIONS::increment, aCommit, evt->Parameter<ACTIONS::INCREMENT>() );
|
|
else
|
|
m_toolMgr->RunSynchronousAction( ACTIONS::increment, aCommit, ACTIONS::INCREMENT { 1, 0 } );
|
|
}
|
|
else if( ZONE_FILLER_TOOL::IsZoneFillAction( evt )
|
|
|| evt->IsAction( &PCB_ACTIONS::moveExact )
|
|
|| evt->IsAction( &PCB_ACTIONS::moveWithReference )
|
|
|| evt->IsAction( &PCB_ACTIONS::copyWithReference )
|
|
|| evt->IsAction( &PCB_ACTIONS::positionRelative )
|
|
|| evt->IsAction( &PCB_ACTIONS::positionRelativeInteractively )
|
|
|| evt->IsAction( &ACTIONS::redo ) )
|
|
{
|
|
wxBell();
|
|
}
|
|
else
|
|
{
|
|
evt->SetPassEvent();
|
|
}
|
|
|
|
} while( ( evt = Wait() ) ); // Assignment (instead of equality test) is intentional
|
|
|
|
// Clear temporary COURTYARD_CONFLICT flag and ensure the conflict shadow is cleared
|
|
if( showCourtyardConflicts )
|
|
drc_on_move->ClearConflicts( m_toolMgr->GetView() );
|
|
|
|
controls->ForceCursorPosition( false );
|
|
controls->ShowCursor( false );
|
|
controls->SetAutoPan( false );
|
|
|
|
m_dragging = false;
|
|
|
|
// Discard reference point when selection is "dropped" onto the board
|
|
selection.ClearReferencePoint();
|
|
|
|
// Unselect all items to clear selection flags and then re-select the originally selected
|
|
// items.
|
|
m_toolMgr->RunAction( ACTIONS::selectionClear );
|
|
|
|
if( restore_state )
|
|
{
|
|
if( sel_items.size() == 1 && sel_items.back()->Type() == PCB_GENERATOR_T )
|
|
{
|
|
m_toolMgr->RunSynchronousAction( PCB_ACTIONS::genRevertEdit, aCommit,
|
|
static_cast<PCB_GENERATOR*>( sel_items.back() ) );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if( sel_items.size() == 1 && sel_items.back()->Type() == PCB_GENERATOR_T )
|
|
{
|
|
m_toolMgr->RunSynchronousAction( PCB_ACTIONS::genPushEdit, aCommit,
|
|
static_cast<PCB_GENERATOR*>( sel_items.back() ) );
|
|
}
|
|
|
|
EDA_ITEMS oItems( orig_items.begin(), orig_items.end() );
|
|
m_toolMgr->RunAction<EDA_ITEMS*>( ACTIONS::selectItems, &oItems );
|
|
}
|
|
|
|
// Remove the dynamic ratsnest from the screen
|
|
m_toolMgr->RunAction( PCB_ACTIONS::hideLocalRatsnest );
|
|
|
|
editFrame->PopTool( pushedEvent );
|
|
editFrame->GetCanvas()->SetCurrentCursor( KICURSOR::ARROW );
|
|
|
|
return !restore_state;
|
|
}
|
|
|