mirror of
https://gitlab.com/kicad/code/kicad.git
synced 2025-09-14 10:13:19 +02:00
Also adds Cancel context menu items for Place Footprint, Place Target, Place Drill Origin and Place Grid Origin tools, as well as the standard Zoom and Grid choices. Removes the Paste context menu item from the Place Drill and Place Grid Origin tools. Fixes: lp:1568396 * https://bugs.launchpad.net/kicad/+bug/1568396
1369 lines
43 KiB
C++
1369 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_modules.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 Settings to Current Settings" ),
|
|
_( "Copies the properties of selected pad to the current template pad settings." ) );
|
|
|
|
TOOL_ACTION PCB_ACTIONS::copySettingsToPads( "pcbnew.InteractiveEdit.copySettingsToPads",
|
|
AS_GLOBAL, 0,
|
|
_( "Copy Current Settings to Pads" ),
|
|
_( "Copies the current template pad settings to the selected pad(s)." ) );
|
|
|
|
TOOL_ACTION PCB_ACTIONS::globalEditPads( "pcbnew.InteractiveEdit.globalPadEdit",
|
|
AS_GLOBAL, 0,
|
|
_( "Global Pad Edition" ),
|
|
_( "Changes pad properties globally." ), 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 ¶ms )
|
|
{
|
|
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::selectConnection, true );
|
|
selection = m_selectionTool->RequestSelection( SELECTION_DELETABLE | SELECTION_SANITIZE_PADS );
|
|
}
|
|
|
|
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_MODULE 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 );
|
|
|
|
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;
|
|
}
|
|
|