kicad/pcbnew/tools/edit_tool.cpp

1371 lines
43 KiB
C++

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2013-2017 CERN
* @author Maciej Suminski <maciej.suminski@cern.ch>
* @author Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
* Copyright (C) 2017 KiCad Developers, see CHANGELOG.TXT for contributors.
*
* 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 <limits>
#include <class_board.h>
#include <class_module.h>
#include <class_edge_mod.h>
#include <class_zone.h>
#include <collectors.h>
#include <pcb_edit_frame.h>
#include <kiway.h>
#include <class_draw_panel_gal.h>
#include <footprint_edit_frame.h>
#include <array_creator.h>
#include <pcbnew_id.h>
#include <tool/tool_manager.h>
#include <view/view_controls.h>
#include <view/view.h>
#include <gal/graphics_abstraction_layer.h>
#include <connectivity_data.h>
#include <confirm.h>
#include <bitmaps.h>
#include <hotkeys.h>
#include <cassert>
#include <functional>
using namespace std::placeholders;
#include "pcb_actions.h"
#include "selection_tool.h"
#include "edit_tool.h"
#include "picker_tool.h"
#include "grid_helper.h"
#include "kicad_clipboard.h"
#include "pcbnew_control.h"
#include <router/router_tool.h>
#include <dialogs/dialog_move_exact.h>
#include <dialogs/dialog_track_via_properties.h>
#include <dialogs/dialog_exchange_footprints.h>
#include <tools/tool_event_utils.h>
#include <preview_items/ruler_item.h>
#include <board_commit.h>
// Edit tool actions
TOOL_ACTION PCB_ACTIONS::editFootprintInFpEditor( "pcbnew.InteractiveEdit.editFootprintInFpEditor",
AS_GLOBAL, TOOL_ACTION::LegacyHotKey( HK_EDIT_MODULE_WITH_MODEDIT ),
_( "Open in Footprint Editor" ),
_( "Opens the selected footprint in the Footprint Editor" ),
module_editor_xpm );
TOOL_ACTION PCB_ACTIONS::copyPadToSettings( "pcbnew.InteractiveEdit.copyPadToSettings",
AS_GLOBAL, 0,
_( "Copy Pad Properties to Default Pad Properties" ),
_( "Copies the properties of the selected pad to the default pad properties." ) );
TOOL_ACTION PCB_ACTIONS::copySettingsToPads( "pcbnew.InteractiveEdit.copySettingsToPads",
AS_GLOBAL, 0,
_( "Copy Default Pad Properties to Pads" ),
_( "Copies the default pad properties to the selected pad(s)." ) );
TOOL_ACTION PCB_ACTIONS::globalEditPads( "pcbnew.InteractiveEdit.globalPadEdit",
AS_GLOBAL, 0,
_( "Push Pad Settings..." ),
_( "Copies the selected pad's properties to all pads in its footprint (or similar footprints)." ),
push_pad_settings_xpm );
TOOL_ACTION PCB_ACTIONS::editActivate( "pcbnew.InteractiveEdit",
AS_GLOBAL, 0,
_( "Edit Activate" ), "", move_xpm, AF_ACTIVATE );
TOOL_ACTION PCB_ACTIONS::move( "pcbnew.InteractiveEdit.move",
AS_GLOBAL, TOOL_ACTION::LegacyHotKey( HK_MOVE_ITEM ),
_( "Move" ), _( "Moves the selected item(s)" ), move_xpm, AF_ACTIVATE );
TOOL_ACTION PCB_ACTIONS::duplicate( "pcbnew.InteractiveEdit.duplicate",
AS_GLOBAL, TOOL_ACTION::LegacyHotKey( HK_DUPLICATE_ITEM ),
_( "Duplicate" ), _( "Duplicates the selected item(s)" ), duplicate_xpm );
TOOL_ACTION PCB_ACTIONS::duplicateIncrement( "pcbnew.InteractiveEdit.duplicateIncrementPads",
AS_GLOBAL, TOOL_ACTION::LegacyHotKey( HK_DUPLICATE_ITEM_AND_INCREMENT ),
_( "Duplicate" ), _( "Duplicates the selected item(s), incrementing pad numbers" ) );
TOOL_ACTION PCB_ACTIONS::moveExact( "pcbnew.InteractiveEdit.moveExact",
AS_GLOBAL, TOOL_ACTION::LegacyHotKey( HK_MOVE_ITEM_EXACT ),
_( "Move Exactly..." ), _( "Moves the selected item(s) by an exact amount" ),
move_exactly_xpm );
TOOL_ACTION PCB_ACTIONS::createArray( "pcbnew.InteractiveEdit.createArray",
AS_GLOBAL, TOOL_ACTION::LegacyHotKey( HK_CREATE_ARRAY ),
_( "Create Array..." ), _( "Create array" ), array_xpm, AF_ACTIVATE );
TOOL_ACTION PCB_ACTIONS::rotateCw( "pcbnew.InteractiveEdit.rotateCw",
AS_GLOBAL, MD_SHIFT + 'R',
_( "Rotate Clockwise" ), _( "Rotates selected item(s) clockwise" ),
rotate_cw_xpm, AF_NONE, (void*) -1 );
TOOL_ACTION PCB_ACTIONS::rotateCcw( "pcbnew.InteractiveEdit.rotateCcw",
AS_GLOBAL, TOOL_ACTION::LegacyHotKey( HK_ROTATE_ITEM ),
_( "Rotate Counterclockwise" ), _( "Rotates selected item(s) counterclockwise" ),
rotate_ccw_xpm, AF_NONE, (void*) 1 );
TOOL_ACTION PCB_ACTIONS::flip( "pcbnew.InteractiveEdit.flip",
AS_GLOBAL, TOOL_ACTION::LegacyHotKey( HK_FLIP_ITEM ),
_( "Flip" ), _( "Flips selected item(s)" ), swap_layer_xpm );
TOOL_ACTION PCB_ACTIONS::mirror( "pcbnew.InteractiveEdit.mirror",
AS_GLOBAL, 0,
_( "Mirror" ), _( "Mirrors selected item" ), mirror_h_xpm );
TOOL_ACTION PCB_ACTIONS::remove( "pcbnew.InteractiveEdit.remove",
AS_GLOBAL, TOOL_ACTION::LegacyHotKey( HK_BACK_SPACE ),
_( "Delete" ), _( "Deletes selected item(s)" ), delete_xpm,
AF_NONE, (void*) REMOVE_FLAGS::NORMAL );
TOOL_ACTION PCB_ACTIONS::removeAlt( "pcbnew.InteractiveEdit.removeAlt",
AS_GLOBAL, TOOL_ACTION::LegacyHotKey( HK_DELETE ),
_( "Delete (Alternative)" ), _( "Deletes selected item(s)" ), delete_xpm,
AF_NONE, (void*) REMOVE_FLAGS::ALT );
TOOL_ACTION PCB_ACTIONS::updateFootprints( "pcbnew.InteractiveEdit.updateFootprints",
AS_GLOBAL, 0,
_( "Update Footprint..." ), _( "Update the footprint from the library" ),
reload_xpm );
TOOL_ACTION PCB_ACTIONS::exchangeFootprints( "pcbnew.InteractiveEdit.ExchangeFootprints",
AS_GLOBAL, 0,
_( "Change Footprint..." ), _( "Assign a different footprint from the library" ),
exchange_xpm );
TOOL_ACTION PCB_ACTIONS::properties( "pcbnew.InteractiveEdit.properties",
AS_GLOBAL, TOOL_ACTION::LegacyHotKey( HK_EDIT_ITEM ),
_( "Properties..." ), _( "Displays item properties dialog" ), config_xpm );
TOOL_ACTION PCB_ACTIONS::selectionModified( "pcbnew.InteractiveEdit.ModifiedSelection",
AS_GLOBAL, 0,
"", "", nullptr, AF_NOTIFY );
TOOL_ACTION PCB_ACTIONS::measureTool( "pcbnew.InteractiveEdit.measureTool",
AS_GLOBAL, MD_CTRL + MD_SHIFT + 'M',
_( "Measuring Tool" ), _( "Interactively measure distance between points" ),
nullptr, AF_ACTIVATE );
TOOL_ACTION PCB_ACTIONS::copyToClipboard( "pcbnew.InteractiveEdit.CopyToClipboard",
AS_GLOBAL, MD_CTRL + int( 'C' ),
_( "Copy" ), _( "Copy selected content to clipboard" ),
copy_xpm );
TOOL_ACTION PCB_ACTIONS::cutToClipboard( "pcbnew.InteractiveEdit.CutToClipboard",
AS_GLOBAL, MD_CTRL + int( 'X' ),
_( "Cut" ), _( "Cut selected content to clipboard" ),
cut_xpm );
static wxPoint getAnchorPoint( const SELECTION &selection, const MOVE_PARAMETERS &params )
{
wxPoint anchorPoint;
if( params.origin == RELATIVE_TO_CURRENT_POSITION )
{
return wxPoint( 0, 0 );
}
// set default anchor
VECTOR2I rp = selection.GetCenter();
anchorPoint = wxPoint( rp.x, rp.y );
// If the anchor is not ANCHOR_FROM_LIBRARY then the user applied an override.
// Also run through this block if only one item is slected because it may be a module,
// in which case we want something different than the center of the selection
if( ( params.anchor != ANCHOR_FROM_LIBRARY ) || ( selection.GetSize() == 1 ) )
{
BOARD_ITEM* topLeftItem = static_cast<BOARD_ITEM*>( selection.GetTopLeftModule() );
// no module found if the GetTopLeftModule() returns null
if( topLeftItem != nullptr )
{
if( topLeftItem->Type() == PCB_MODULE_T )
{
// Cast to module to allow access to the pads
MODULE* mod = static_cast<MODULE*>( topLeftItem );
switch( params.anchor )
{
case ANCHOR_FROM_LIBRARY:
anchorPoint = mod->GetPosition();
break;
case ANCHOR_TOP_LEFT_PAD:
topLeftItem = mod->GetTopLeftPad();
break;
case ANCHOR_CENTER_FOOTPRINT:
anchorPoint = mod->GetFootprintRect().GetCenter();
break;
}
}
if( topLeftItem->Type() == PCB_PAD_T )
{
if( static_cast<D_PAD*>( topLeftItem )->GetAttribute() == PAD_ATTRIB_SMD )
{
// Use the top left corner of SMD pads as an anchor instead of the center
anchorPoint = topLeftItem->GetBoundingBox().GetPosition();
}
else
{
anchorPoint = topLeftItem->GetPosition();
}
}
}
else // no module found in the selection
{
// in a selection of non-modules
if( params.anchor == ANCHOR_TOP_LEFT_PAD )
{
// approach the top left pad override for non-modules by using the position of
// the topleft item as an anchor
topLeftItem = static_cast<BOARD_ITEM*>( selection.GetTopLeftItem() );
anchorPoint = topLeftItem->GetPosition();
}
}
}
return anchorPoint;
}
EDIT_TOOL::EDIT_TOOL() :
PCB_TOOL( "pcbnew.InteractiveEdit" ), m_selectionTool( NULL ),
m_dragging( false )
{
}
void EDIT_TOOL::Reset( RESET_REASON aReason )
{
m_dragging = false;
if( aReason != RUN )
m_commit.reset( new BOARD_COMMIT( this ) );
}
bool EDIT_TOOL::Init()
{
// Find the selection tool, so they can cooperate
m_selectionTool = static_cast<SELECTION_TOOL*>( m_toolMgr->FindTool( "pcbnew.InteractiveSelection" ) );
if( !m_selectionTool )
{
DisplayError( NULL, wxT( "pcbnew.InteractiveSelection tool is not available" ) );
return false;
}
auto editingModuleCondition = [ this ] ( const SELECTION& aSelection ) {
return m_editModules;
};
auto singleModuleCondition = SELECTION_CONDITIONS::OnlyType( PCB_MODULE_T )
&& SELECTION_CONDITIONS::Count( 1 );
auto noActiveToolCondition = [ this ] ( const SELECTION& aSelection ) {
return ( frame()->GetToolId() == ID_NO_TOOL_SELECTED );
};
// Add context menu entries that are displayed when selection tool is active
CONDITIONAL_MENU& menu = m_selectionTool->GetToolMenu().GetMenu();
menu.AddItem( PCB_ACTIONS::move, SELECTION_CONDITIONS::NotEmpty );
menu.AddItem( PCB_ACTIONS::drag45Degree, SELECTION_CONDITIONS::OnlyTypes( GENERAL_COLLECTOR::Tracks ) );
menu.AddItem( PCB_ACTIONS::dragFreeAngle, SELECTION_CONDITIONS::OnlyTypes( GENERAL_COLLECTOR::Tracks ) );
menu.AddItem( PCB_ACTIONS::rotateCcw, SELECTION_CONDITIONS::NotEmpty );
menu.AddItem( PCB_ACTIONS::rotateCw, SELECTION_CONDITIONS::NotEmpty );
menu.AddItem( PCB_ACTIONS::flip, SELECTION_CONDITIONS::NotEmpty );
menu.AddItem( PCB_ACTIONS::remove, SELECTION_CONDITIONS::NotEmpty );
menu.AddItem( PCB_ACTIONS::properties, SELECTION_CONDITIONS::Count( 1 )
|| SELECTION_CONDITIONS::OnlyTypes( GENERAL_COLLECTOR::Tracks ) );
menu.AddItem( PCB_ACTIONS::moveExact, SELECTION_CONDITIONS::NotEmpty );
menu.AddItem( PCB_ACTIONS::positionRelative, SELECTION_CONDITIONS::NotEmpty );
menu.AddItem( PCB_ACTIONS::duplicate, SELECTION_CONDITIONS::NotEmpty );
menu.AddItem( PCB_ACTIONS::createArray, SELECTION_CONDITIONS::NotEmpty );
menu.AddItem( PCB_ACTIONS::copyToClipboard, SELECTION_CONDITIONS::NotEmpty );
menu.AddItem( PCB_ACTIONS::cutToClipboard, SELECTION_CONDITIONS::NotEmpty );
// Selection tool handles the context menu for some other tools, such as the Picker.
// Don't add things like Paste when another tool is active.
menu.AddItem( PCB_ACTIONS::pasteFromClipboard, noActiveToolCondition );
menu.AddSeparator( noActiveToolCondition );
// Mirror only available in modedit
menu.AddItem( PCB_ACTIONS::mirror, editingModuleCondition && SELECTION_CONDITIONS::NotEmpty );
menu.AddItem( PCB_ACTIONS::createPadFromShapes, editingModuleCondition && SELECTION_CONDITIONS::NotEmpty );
menu.AddItem( PCB_ACTIONS::explodePadToShapes, editingModuleCondition && SELECTION_CONDITIONS::NotEmpty );
// Footprint actions
menu.AddItem( PCB_ACTIONS::editFootprintInFpEditor, singleModuleCondition );
menu.AddItem( PCB_ACTIONS::updateFootprints, singleModuleCondition );
menu.AddItem( PCB_ACTIONS::exchangeFootprints, singleModuleCondition );
return true;
}
bool EDIT_TOOL::invokeInlineRouter( int aDragMode )
{
auto theRouter = static_cast<ROUTER_TOOL*>( m_toolMgr->FindTool( "pcbnew.InteractiveRouter" ) );
if( !theRouter )
return false;
// make sure we don't accidentally invoke inline routing mode while the router is already active!
if( theRouter->IsToolActive() )
return false;
if( theRouter->CanInlineDrag() )
{
m_toolMgr->RunAction( PCB_ACTIONS::routerInlineDrag, true, aDragMode );
return true;
}
return false;
}
bool EDIT_TOOL::isInteractiveDragEnabled() const
{
auto theRouter = static_cast<ROUTER_TOOL*>( m_toolMgr->FindTool( "pcbnew.InteractiveRouter" ) );
return theRouter ? theRouter->Router()->Settings().InlineDragEnabled() : false;
}
int EDIT_TOOL::Drag( const TOOL_EVENT& aEvent )
{
int mode = PNS::DM_ANY;
if( aEvent.IsAction( &PCB_ACTIONS::dragFreeAngle ) )
mode |= PNS::DM_FREE_ANGLE;
invokeInlineRouter( mode );
return 0;
}
int EDIT_TOOL::Main( const TOOL_EVENT& aEvent )
{
KIGFX::VIEW_CONTROLS* controls = getViewControls();
PCB_BASE_EDIT_FRAME* editFrame = getEditFrame<PCB_BASE_EDIT_FRAME>();
VECTOR2I originalCursorPos = controls->GetCursorPosition();
// 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)
auto& selection = m_selectionTool->RequestSelection( SELECTION_DEFAULT );
if( selection.Empty() )
return 0;
bool unselect = selection.IsHover();
if( m_dragging )
return 0;
Activate();
m_dragging = false; // Are selected items being dragged?
bool restore = false; // Should items' state be restored when finishing the tool?
controls->ShowCursor( true );
controls->SetSnapping( true );
controls->SetAutoPan( true );
// cumulative translation
VECTOR2I totalMovement;
GRID_HELPER grid( editFrame );
OPT_TOOL_EVENT evt = aEvent;
VECTOR2I prevPos;
// Main loop: keep receiving events
do
{
bool matchingAction = evt->IsAction( &PCB_ACTIONS::editActivate )
|| evt->IsAction( &PCB_ACTIONS::move );
if( matchingAction
|| evt->IsMotion() || evt->IsDrag( BUT_LEFT ) )
{
if( selection.Empty() )
break;
auto curr_item = static_cast<BOARD_ITEM*>( selection.Front() );
if( m_dragging && evt->Category() == TC_MOUSE )
{
m_cursor = grid.BestSnapAnchor( evt->Position(), curr_item );
controls->ForceCursorPosition( true, m_cursor );
VECTOR2I movement( m_cursor - prevPos );
selection.SetReferencePoint(m_cursor);
totalMovement += movement;
prevPos = m_cursor;
// Drag items to the current cursor position
for( auto item : selection )
{
static_cast<BOARD_ITEM*>( item )->Move( movement );
}
}
else if( !m_dragging ) // Prepare to start dragging
{
bool invokedRouter = false;
if ( !evt->IsAction( &PCB_ACTIONS::move ) && isInteractiveDragEnabled() )
invokedRouter = invokeInlineRouter( PNS::DM_ANY );
if( !invokedRouter )
{
// deal with locked items (override lock or abort the operation)
SELECTION_LOCK_FLAGS lockFlags = m_selectionTool->CheckLock();
if( lockFlags == SELECTION_LOCKED )
break;
// Save items, so changes can be undone
for( auto item : selection )
m_commit->Modify( item );
m_cursor = controls->GetCursorPosition();
if ( selection.HasReferencePoint() )
{
// start moving with the reference point attached to the cursor
grid.SetAuxAxes( false );
auto delta = m_cursor - selection.GetReferencePoint();
// Drag items to the current cursor position
for( auto item : selection )
static_cast<BOARD_ITEM*>( item )->Move( delta );
selection.SetReferencePoint( m_cursor );
}
else if( selection.Size() == 1 )
{
// Set the current cursor position to the first dragged item origin, so the
// movement vector could be computed later
updateModificationPoint( selection );
m_cursor = grid.BestDragOrigin( originalCursorPos, curr_item );
grid.SetAuxAxes( true, m_cursor );
}
else
{
updateModificationPoint( selection );
m_cursor = grid.Align( m_cursor );
}
controls->SetCursorPosition( m_cursor, false );
prevPos = m_cursor;
controls->SetAutoPan( true );
m_dragging = true;
}
}
m_toolMgr->RunAction( PCB_ACTIONS::selectionModified, false );
}
else if( evt->IsCancel() || evt->IsActivate() )
{
restore = true; // Cancelling the tool means that items have to be restored
break; // Finish
}
else if( evt->Action() == TA_UNDO_REDO_PRE )
{
unselect = true;
break;
}
// Dispatch TOOL_ACTIONs
else if( evt->Category() == TC_COMMAND )
{
if( evt->IsAction( &PCB_ACTIONS::remove ) )
{
// exit the loop, as there is no further processing for removed items
break;
}
else if( evt->IsAction( &PCB_ACTIONS::duplicate ) )
{
// On duplicate, stop moving this item
// The duplicate tool should then select the new item and start
// a new move procedure
break;
}
else if( evt->IsAction( &PCB_ACTIONS::moveExact ) )
{
// Can't do this, because the selection will then contain
// stale pointers and it will all go horribly wrong...
//editFrame->RestoreCopyFromUndoList( dummy );
//
// So, instead, reset the position manually
for( auto item : selection )
{
BOARD_ITEM* i = static_cast<BOARD_ITEM*>( item );
auto delta = VECTOR2I( i->GetPosition() ) - totalMovement;
i->SetPosition( wxPoint( delta.x, delta.y ) );
// And what about flipping and rotation?
// for now, they won't be undone, but maybe that is how
// it should be, so you can flip and move exact in the
// same action?
}
// This causes a double event, so we will get the dialogue
// correctly, somehow - why does Rotate not?
//MoveExact( aEvent );
break; // exit the loop - we move exactly, so we have finished moving
}
}
else if( evt->IsMouseUp( BUT_LEFT ) || evt->IsClick( BUT_LEFT ) )
{
break; // Finish
}
} while( ( evt = Wait() ) ); //Should be assignment not equality test
controls->ForceCursorPosition( false );
controls->ShowCursor( false );
controls->SetSnapping( false );
controls->SetAutoPan( false );
m_dragging = false;
// Discard reference point when selection is "dropped" onto the board (ie: not dragging anymore)
selection.ClearReferencePoint();
if( unselect || restore )
m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
if( restore )
m_commit->Revert();
else
m_commit->Push( _( "Drag" ) );
return 0;
}
bool EDIT_TOOL::changeTrackWidthOnClick( const SELECTION& selection )
{
if ( selection.Size() == 1 && frame()->Settings().m_editActionChangesTrackWidth )
{
auto item = static_cast<BOARD_ITEM *>( selection[0] );
m_commit->Modify( item );
if( auto via = dyn_cast<VIA*>( item ) )
{
int new_width, new_drill;
if( via->GetViaType() == VIA_MICROVIA )
{
auto net = via->GetNet();
new_width = net->GetMicroViaSize();
new_drill = net->GetMicroViaDrillSize();
}
else
{
new_width = board()->GetDesignSettings().GetCurrentViaSize();
new_drill = board()->GetDesignSettings().GetCurrentViaDrill();
}
via->SetDrill( new_drill );
via->SetWidth( new_width );
}
else if ( auto track = dyn_cast<TRACK*>( item ) )
{
int new_width = board()->GetDesignSettings().GetCurrentTrackWidth();
track->SetWidth( new_width );
}
m_commit->Push( _("Edit track width/via size") );
return true;
}
return false;
}
int EDIT_TOOL::Properties( const TOOL_EVENT& aEvent )
{
PCB_BASE_EDIT_FRAME* editFrame = getEditFrame<PCB_BASE_EDIT_FRAME>();
const auto& selection = m_selectionTool->RequestSelection(
SELECTION_EDITABLE | SELECTION_DELETABLE | SELECTION_FORCE_UNLOCK );
if( selection.Empty() )
return 0;
// Tracks & vias are treated in a special way:
if( ( SELECTION_CONDITIONS::OnlyTypes( GENERAL_COLLECTOR::Tracks ) )( selection ) )
{
if ( !changeTrackWidthOnClick( selection ) )
{
DIALOG_TRACK_VIA_PROPERTIES dlg( editFrame, selection );
if( dlg.ShowModal() )
{
dlg.Apply( *m_commit );
m_commit->Push( _( "Edit track/via properties" ) );
}
}
}
else if( selection.Size() == 1 ) // Properties are displayed when there is only one item selected
{
// Display properties dialog
BOARD_ITEM* item = static_cast<BOARD_ITEM*>( selection.Front() );
// Some of properties dialogs alter pointers, so we should deselect them
m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
// Store flags, so they can be restored later
STATUS_FLAGS flags = item->GetFlags();
item->ClearFlags();
// Do not handle undo buffer, it is done by the properties dialogs @todo LEGACY
// Display properties dialog provided by the legacy canvas frame
editFrame->OnEditItemRequest( NULL, item );
m_toolMgr->RunAction( PCB_ACTIONS::selectionModified, true );
item->SetFlags( flags );
}
if( selection.IsHover() )
m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
return 0;
}
int EDIT_TOOL::Rotate( const TOOL_EVENT& aEvent )
{
PCB_BASE_EDIT_FRAME* editFrame = getEditFrame<PCB_BASE_EDIT_FRAME>();
auto& selection = m_selectionTool->RequestSelection();
if( selection.Empty() )
return 0;
if( m_selectionTool->CheckLock() == SELECTION_LOCKED )
return 0;
updateModificationPoint( selection );
const int rotateAngle = TOOL_EVT_UTILS::GetEventRotationAngle( *editFrame, aEvent );
for( auto item : selection )
{
m_commit->Modify( item );
static_cast<BOARD_ITEM*>( item )->Rotate( selection.GetReferencePoint(), rotateAngle );
}
if( !m_dragging )
m_commit->Push( _( "Rotate" ) );
if( selection.IsHover() && !m_dragging )
m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
m_toolMgr->RunAction( PCB_ACTIONS::selectionModified, true );
return 0;
}
/*!
* Mirror a point about the vertical axis passing through another point
*/
static wxPoint mirrorPointX( const wxPoint& aPoint, const wxPoint& aMirrorPoint )
{
wxPoint mirrored = aPoint;
mirrored.x -= aMirrorPoint.x;
mirrored.x = -mirrored.x;
mirrored.x += aMirrorPoint.x;
return mirrored;
}
/**
* Mirror a pad in the vertical axis passing through a point
*/
static void mirrorPadX( D_PAD& aPad, const wxPoint& aMirrorPoint )
{
wxPoint tmpPt = mirrorPointX( aPad.GetPosition(), aMirrorPoint );
aPad.SetPosition( tmpPt );
aPad.SetX0( aPad.GetPosition().x );
tmpPt = aPad.GetOffset();
tmpPt.x = -tmpPt.x;
aPad.SetOffset( tmpPt );
auto tmpz = aPad.GetDelta();
tmpz.x = -tmpz.x;
aPad.SetDelta( tmpz );
aPad.SetOrientation( -aPad.GetOrientation() );
}
int EDIT_TOOL::Mirror( const TOOL_EVENT& aEvent )
{
auto& selection = m_selectionTool->RequestSelection();
if( m_selectionTool->CheckLock() == SELECTION_LOCKED )
return 0;
if( selection.Empty() )
return 0;
updateModificationPoint( selection );
auto refPoint = selection.GetReferencePoint();
wxPoint mirrorPoint( refPoint.x, refPoint.y );
for( auto item : selection )
{
// only modify items we can mirror
switch( item->Type() )
{
case PCB_MODULE_EDGE_T:
case PCB_MODULE_TEXT_T:
case PCB_PAD_T:
m_commit->Modify( item );
break;
default:
continue;
}
// modify each object as necessary
switch( item->Type() )
{
case PCB_MODULE_EDGE_T:
{
auto& edge = static_cast<EDGE_MODULE&>( *item );
edge.Mirror( mirrorPoint, false );
break;
}
case PCB_MODULE_TEXT_T:
{
auto& modText = static_cast<TEXTE_MODULE&>( *item );
modText.Mirror( mirrorPoint, false );
break;
}
case PCB_PAD_T:
{
auto& pad = static_cast<D_PAD&>( *item );
mirrorPadX( pad, mirrorPoint );
break;
}
default:
// it's likely the commit object is wrong if you get here
assert( false );
break;
}
}
if( !m_dragging )
m_commit->Push( _( "Mirror" ) );
if( selection.IsHover() && !m_dragging )
m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
m_toolMgr->RunAction( PCB_ACTIONS::selectionModified, true );
return 0;
}
int EDIT_TOOL::Flip( const TOOL_EVENT& aEvent )
{
auto& selection = m_selectionTool->RequestSelection();
if( m_selectionTool->CheckLock() == SELECTION_LOCKED )
return 0;
if( selection.Empty() )
return 0;
updateModificationPoint( selection );
auto modPoint = selection.GetReferencePoint();
for( auto item : selection )
{
m_commit->Modify( item );
static_cast<BOARD_ITEM*>( item )->Flip( modPoint );
}
if( !m_dragging )
m_commit->Push( _( "Flip" ) );
if( selection.IsHover() && !m_dragging )
m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
m_toolMgr->RunAction( PCB_ACTIONS::selectionModified, true );
return 0;
}
int EDIT_TOOL::Remove( const TOOL_EVENT& aEvent )
{
// get a copy instead of reference (as we're going to clear the selectio before removing items)
auto selection = m_selectionTool->RequestSelection( SELECTION_DELETABLE | SELECTION_SANITIZE_PADS );
if( m_selectionTool->CheckLock() == SELECTION_LOCKED )
return 0;
// is this "alternative" remove?
const bool isAlt = aEvent.Parameter<intptr_t>() == (int) PCB_ACTIONS::REMOVE_FLAGS::ALT;
// in "alternative" mode, deletion is not just a simple list of selected items,
// it removes whole tracks, not just segments
if( isAlt && selection.IsHover()
&& ( selection.HasType( PCB_TRACE_T ) || selection.HasType( PCB_VIA_T ) ) )
{
m_toolMgr->RunAction( PCB_ACTIONS::expandSelectedConnection, true );
selection = m_selectionTool->GetSelection();
}
if( selection.Empty() )
return 0;
// As we are about to remove items, they have to be removed from the selection first
m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
for( auto item : selection )
m_commit->Remove( item );
m_commit->Push( _( "Delete" ) );
return 0;
}
int EDIT_TOOL::MoveExact( const TOOL_EVENT& aEvent )
{
const auto& selection = m_selectionTool->RequestSelection();
if( m_selectionTool->CheckLock() == SELECTION_LOCKED )
return 0;
if( selection.Empty() )
return 0;
PCB_BASE_FRAME* editFrame = getEditFrame<PCB_BASE_FRAME>();
MOVE_PARAMETERS params;
params.editingFootprint = m_editModules;
DIALOG_MOVE_EXACT dialog( editFrame, params );
int ret = dialog.ShowModal();
if( ret == wxID_OK )
{
VECTOR2I rp = selection.GetCenter();
wxPoint rotPoint( rp.x, rp.y );
wxPoint anchorPoint = getAnchorPoint( selection, params );
wxPoint finalMoveVector = params.translation - anchorPoint;
// Make sure the rotation is from the right reference point
rotPoint += finalMoveVector;
for( auto item : selection )
{
m_commit->Modify( item );
static_cast<BOARD_ITEM*>( item )->Move( finalMoveVector );
static_cast<BOARD_ITEM*>( item )->Rotate( rotPoint, params.rotation );
if( !m_dragging )
getView()->Update( item );
}
m_commit->Push( _( "Move exact" ) );
if( selection.IsHover() )
m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
m_toolMgr->RunAction( PCB_ACTIONS::selectionModified, true );
}
return 0;
}
int EDIT_TOOL::Duplicate( const TOOL_EVENT& aEvent )
{
bool increment = aEvent.IsAction( &PCB_ACTIONS::duplicateIncrement );
// Be sure that there is at least one item that we can modify
const auto& selection = m_selectionTool->RequestSelection( SELECTION_DELETABLE | SELECTION_SANITIZE_PADS );
if( selection.Empty() )
return 0;
// we have a selection to work on now, so start the tool process
PCB_BASE_EDIT_FRAME* editFrame = getEditFrame<PCB_BASE_EDIT_FRAME>();
std::vector<BOARD_ITEM*> new_items;
new_items.reserve( selection.Size() );
BOARD_ITEM* orig_item = nullptr;
BOARD_ITEM* dupe_item = nullptr;
// Each selected item is duplicated and pushed to new_items list
// Old selection is cleared, and new items are then selected.
for( auto item : selection )
{
if( !item )
continue;
orig_item = static_cast<BOARD_ITEM*>( item );
if( m_editModules )
{
dupe_item = editFrame->GetBoard()->m_Modules->Duplicate( orig_item, increment );
}
else
{
#if 0
// @TODO: see if we allow zone duplication here
// Duplicate zones is especially tricky (overlaping zones must be merged)
// so zones are not duplicated
if( item->Type() != PCB_ZONE_AREA_T )
#endif
dupe_item = editFrame->GetBoard()->Duplicate( orig_item );
}
if( dupe_item )
{
// Clear the selection flag here, otherwise the SELECTION_TOOL
// will not properly select it later on
dupe_item->ClearSelected();
new_items.push_back( dupe_item );
m_commit->Add( dupe_item );
}
}
// Clear the old selection first
m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
// Select the new items
m_toolMgr->RunAction( PCB_ACTIONS::selectItems, true, &new_items );
// record the new items as added
if( !selection.Empty() )
{
editFrame->DisplayToolMsg( wxString::Format( _( "Duplicated %d item(s)" ),
(int) new_items.size() ) );
// If items were duplicated, pick them up
// this works well for "dropping" copies around and pushes the commit
TOOL_EVENT evt = PCB_ACTIONS::editActivate.MakeEvent();
Main( evt );
}
return 0;
}
class GAL_ARRAY_CREATOR: public ARRAY_CREATOR
{
public:
GAL_ARRAY_CREATOR( PCB_BASE_FRAME& editFrame, bool editModules,
const SELECTION& selection ):
ARRAY_CREATOR( editFrame ),
m_editModules( editModules ),
m_selection( selection )
{}
private:
int getNumberOfItemsToArray() const override
{
// only handle single items
return m_selection.Size();
}
BOARD_ITEM* getNthItemToArray( int n ) const override
{
return static_cast<BOARD_ITEM*>( m_selection[n] );
}
BOARD* getBoard() const override
{
return m_parent.GetBoard();
}
MODULE* getModule() const override
{
// Remember this is valid and used only in the module editor.
// in board editor, the parent of items is usually the board.
return m_editModules ? m_parent.GetBoard()->m_Modules.GetFirst() : NULL;
}
wxPoint getRotationCentre() const override
{
const VECTOR2I rp = m_selection.GetCenter();
return wxPoint( rp.x, rp.y );
}
void prePushAction( BOARD_ITEM* aItem ) override
{
// Because aItem is/can be created from a selected item, and inherits from
// it this state, reset the selected stated of aItem:
aItem->ClearSelected();
if( aItem->Type() == PCB_MODULE_T )
{
static_cast<MODULE*>( aItem )->RunOnChildren( [&] ( BOARD_ITEM* item )
{
item->ClearSelected();
}
);
}
}
void postPushAction( BOARD_ITEM* new_item ) override
{
}
void finalise() override
{
}
bool m_editModules;
const SELECTION& m_selection;
};
int EDIT_TOOL::CreateArray( const TOOL_EVENT& aEvent )
{
const auto& selection = m_selectionTool->RequestSelection();
if( selection.Empty() )
return 0;
// we have a selection to work on now, so start the tool process
PCB_BASE_FRAME* editFrame = getEditFrame<PCB_BASE_FRAME>();
GAL_ARRAY_CREATOR array_creator( *editFrame, m_editModules, selection );
array_creator.Invoke();
return 0;
}
void EDIT_TOOL::FootprintFilter( const VECTOR2I&, GENERAL_COLLECTOR& aCollector )
{
for( int i = aCollector.GetCount() - 1; i >= 0; i-- )
{
BOARD_ITEM* item = static_cast<BOARD_ITEM*>( aCollector[i] );
if( item->Type() != PCB_MODULE_T )
aCollector.Remove( i );
}
}
int EDIT_TOOL::ExchangeFootprints( const TOOL_EVENT& aEvent )
{
const auto& selection = m_selectionTool->RequestSelection( 0, FootprintFilter );
bool updateMode = aEvent.IsAction( &PCB_ACTIONS::updateFootprints );
MODULE* mod = (selection.Empty() ? nullptr : selection.FirstOfKind<MODULE> () );
frame()->SetCurItem( mod );
// Footprint exchange could remove modules, so they have to be
// removed from the selection first
m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
// invoke the exchange dialog process
{
DIALOG_EXCHANGE_FOOTPRINTS dialog( frame(), mod, updateMode );
dialog.ShowQuasiModal();
}
return 0;
}
int EDIT_TOOL::MeasureTool( const TOOL_EVENT& aEvent )
{
auto& view = *getView();
auto& controls = *getViewControls();
Activate();
frame()->SetToolID( EditingModules() ? ID_MODEDIT_MEASUREMENT_TOOL
: ID_PCB_MEASUREMENT_TOOL,
wxCURSOR_PENCIL, _( "Measure distance" ) );
KIGFX::PREVIEW::TWO_POINT_GEOMETRY_MANAGER twoPtMgr;
KIGFX::PREVIEW::RULER_ITEM ruler( twoPtMgr );
view.Add( &ruler );
view.SetVisible( &ruler, false );
bool originSet = false;
controls.ShowCursor( true );
controls.SetSnapping( true );
controls.SetAutoPan( false );
while( auto evt = Wait() )
{
const VECTOR2I cursorPos = controls.GetCursorPosition();
if( evt->IsCancel() || evt->IsActivate() )
{
break;
}
// click or drag starts
else if( !originSet &&
( evt->IsDrag( BUT_LEFT ) || evt->IsClick( BUT_LEFT ) ) )
{
if( !evt->IsDrag( BUT_LEFT ) )
{
twoPtMgr.SetOrigin( cursorPos );
twoPtMgr.SetEnd( cursorPos );
}
controls.CaptureCursor( true );
controls.SetAutoPan( true );
originSet = true;
}
else if( !originSet && evt->IsMotion() )
{
// make sure the origin is set before a drag starts
// otherwise you can miss a step
twoPtMgr.SetOrigin( cursorPos );
twoPtMgr.SetEnd( cursorPos );
}
// second click or mouse up after drag ends
else if( originSet &&
( evt->IsClick( BUT_LEFT ) || evt->IsMouseUp( BUT_LEFT ) ) )
{
originSet = false;
controls.SetAutoPan( false );
controls.CaptureCursor( false );
view.SetVisible( &ruler, false );
}
// move or drag when origin set updates rules
else if( originSet &&
( evt->IsMotion() || evt->IsDrag( BUT_LEFT ) ) )
{
twoPtMgr.SetAngleSnap( evt->Modifier( MD_CTRL ) );
twoPtMgr.SetEnd( cursorPos );
view.SetVisible( &ruler, true );
view.Update( &ruler, KIGFX::GEOMETRY );
}
else if( evt->IsClick( BUT_RIGHT ) )
{
GetManager()->PassEvent();
}
}
view.SetVisible( &ruler, false );
view.Remove( &ruler );
frame()->SetNoToolSelected();
return 0;
}
void EDIT_TOOL::setTransitions()
{
Go( &EDIT_TOOL::Main, PCB_ACTIONS::editActivate.MakeEvent() );
Go( &EDIT_TOOL::Main, PCB_ACTIONS::move.MakeEvent() );
Go( &EDIT_TOOL::Drag, PCB_ACTIONS::drag45Degree.MakeEvent() );
Go( &EDIT_TOOL::Drag, PCB_ACTIONS::dragFreeAngle.MakeEvent() );
Go( &EDIT_TOOL::Rotate, PCB_ACTIONS::rotateCw.MakeEvent() );
Go( &EDIT_TOOL::Rotate, PCB_ACTIONS::rotateCcw.MakeEvent() );
Go( &EDIT_TOOL::Flip, PCB_ACTIONS::flip.MakeEvent() );
Go( &EDIT_TOOL::Remove, PCB_ACTIONS::remove.MakeEvent() );
Go( &EDIT_TOOL::Remove, PCB_ACTIONS::removeAlt.MakeEvent() );
Go( &EDIT_TOOL::Properties, PCB_ACTIONS::properties.MakeEvent() );
Go( &EDIT_TOOL::MoveExact, PCB_ACTIONS::moveExact.MakeEvent() );
Go( &EDIT_TOOL::Duplicate, PCB_ACTIONS::duplicate.MakeEvent() );
Go( &EDIT_TOOL::Duplicate, PCB_ACTIONS::duplicateIncrement.MakeEvent() );
Go( &EDIT_TOOL::CreateArray,PCB_ACTIONS::createArray.MakeEvent() );
Go( &EDIT_TOOL::Mirror, PCB_ACTIONS::mirror.MakeEvent() );
Go( &EDIT_TOOL::editFootprintInFpEditor, PCB_ACTIONS::editFootprintInFpEditor.MakeEvent() );
Go( &EDIT_TOOL::ExchangeFootprints, PCB_ACTIONS::updateFootprints.MakeEvent() );
Go( &EDIT_TOOL::ExchangeFootprints, PCB_ACTIONS::exchangeFootprints.MakeEvent() );
Go( &EDIT_TOOL::MeasureTool, PCB_ACTIONS::measureTool.MakeEvent() );
Go( &EDIT_TOOL::copyToClipboard, PCB_ACTIONS::copyToClipboard.MakeEvent() );
Go( &EDIT_TOOL::cutToClipboard, PCB_ACTIONS::cutToClipboard.MakeEvent() );
}
bool EDIT_TOOL::updateModificationPoint( SELECTION& aSelection )
{
if( aSelection.HasReferencePoint() )
return false;
if( aSelection.Size() == 1 )
{
auto item = static_cast<BOARD_ITEM*>( aSelection.Front() );
auto pos = item->GetPosition();
aSelection.SetReferencePoint( VECTOR2I( pos.x, pos.y ) );
}
else
{
// If EDIT_TOOL is not currently active then it means that the cursor position is not
// updated, so we have to fetch the latest value
if( m_toolMgr->GetCurrentToolId() != m_toolId )
m_cursor = getViewControls()->GetCursorPosition();
aSelection.SetReferencePoint( m_cursor );
}
return true;
}
int EDIT_TOOL::editFootprintInFpEditor( const TOOL_EVENT& aEvent )
{
const auto& selection = m_selectionTool->RequestSelection( 0, FootprintFilter );
if( selection.Empty() )
return 0;
MODULE* mod = selection.FirstOfKind<MODULE>();
if( !mod )
return 0;
PCB_BASE_EDIT_FRAME* editFrame = getEditFrame<PCB_BASE_EDIT_FRAME>();
editFrame->SetCurItem( mod );
if( editFrame->GetCurItem()->GetTimeStamp() == 0 ) // Module Editor needs a non null timestamp
{
editFrame->GetCurItem()->SetTimeStamp( GetNewTimeStamp() );
editFrame->OnModify();
}
FOOTPRINT_EDIT_FRAME* editor = (FOOTPRINT_EDIT_FRAME*) editFrame->Kiway().Player( FRAME_PCB_MODULE_EDITOR, true );
editor->Load_Module_From_BOARD( (MODULE*) editFrame->GetCurItem() );
editFrame->SetCurItem( NULL ); // the current module could be deleted by
editor->Show( true );
editor->Raise(); // Iconize( false );
if( selection.IsHover() )
m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
return 0;
}
bool EDIT_TOOL::pickCopyReferencePoint( VECTOR2I& aP )
{
PICKER_TOOL* picker = m_toolMgr->GetTool<PICKER_TOOL>();
assert( picker );
picker->Activate();
while ( picker->IsPicking() )
Wait();
if( !picker->GetPoint() )
return false;
aP = *picker->GetPoint();
return true;
}
int EDIT_TOOL::copyToClipboard( const TOOL_EVENT& aEvent )
{
CLIPBOARD_IO io;
VECTOR2I refPoint;
Activate();
auto item1 = MSG_PANEL_ITEM( "", _( "Select reference point for the block being copied..." ),
COLOR4D::BLACK );
std::vector<MSG_PANEL_ITEM> msgItems = { item1 };
SELECTION selection = m_selectionTool->RequestSelection();
if( selection.Empty() )
return 0;
frame()->SetMsgPanel( msgItems );
bool rv = pickCopyReferencePoint( refPoint );
frame()->SetMsgPanel( board() );
if( !rv )
return 0;
selection.SetReferencePoint( refPoint );
io.SetBoard( board() );
io.SaveSelection( selection );
return 0;
}
int EDIT_TOOL::cutToClipboard( const TOOL_EVENT& aEvent )
{
copyToClipboard( aEvent );
Remove( aEvent );
return 0;
}