2017-05-10 23:55:03 +10:00
|
|
|
/*
|
|
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
|
|
*
|
2025-01-01 13:30:11 -08:00
|
|
|
* Copyright The KiCad Developers, see AUTHORS.txt for contributors.
|
2017-05-10 23:55:03 +10:00
|
|
|
*
|
|
|
|
* 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
|
|
|
|
*/
|
|
|
|
|
2024-10-29 12:59:27 +08:00
|
|
|
#include "tools/position_relative_tool.h"
|
2017-05-10 23:55:03 +10:00
|
|
|
|
|
|
|
#include <board_commit.h>
|
2021-06-03 19:05:43 +01:00
|
|
|
#include <collectors.h>
|
2024-10-29 12:59:27 +08:00
|
|
|
#include <dialogs/dialog_position_relative.h>
|
|
|
|
#include <dialogs/dialog_set_offset.h>
|
2022-08-22 22:32:42 +01:00
|
|
|
#include <footprint.h>
|
2024-10-29 12:59:27 +08:00
|
|
|
#include <footprint_editor_settings.h>
|
|
|
|
#include <gal/graphics_abstraction_layer.h>
|
|
|
|
#include <kiplatform/ui.h>
|
|
|
|
#include <pad.h>
|
2022-08-22 22:32:42 +01:00
|
|
|
#include <pcb_group.h>
|
2024-10-29 12:59:27 +08:00
|
|
|
#include <preview_items/two_point_assistant.h>
|
|
|
|
#include <preview_items/two_point_geom_manager.h>
|
|
|
|
#include <pcb_painter.h>
|
|
|
|
#include <pgm_base.h>
|
|
|
|
#include <preview_items/ruler_item.h>
|
|
|
|
#include <render_settings.h>
|
|
|
|
#include <settings/settings_manager.h>
|
|
|
|
#include <status_popup.h>
|
|
|
|
#include <tools/pcb_actions.h>
|
|
|
|
#include <tools/pcb_grid_helper.h>
|
|
|
|
#include <tools/pcb_selection_tool.h>
|
|
|
|
#include <tools/pcb_picker_tool.h>
|
|
|
|
#include <view/view_controls.h>
|
2017-05-10 23:55:03 +10:00
|
|
|
|
|
|
|
|
2024-12-31 18:12:20 +08:00
|
|
|
/**
|
|
|
|
* Move each item in the selection by the given vector.
|
|
|
|
*
|
|
|
|
* If any pads are part of a footprint, the whole footprint is moved.
|
|
|
|
*/
|
|
|
|
static void moveSelectionBy( const PCB_SELECTION& aSelection, const VECTOR2I& aMoveVec,
|
|
|
|
BOARD_COMMIT& commit )
|
|
|
|
{
|
|
|
|
for( EDA_ITEM* item : aSelection )
|
|
|
|
{
|
|
|
|
if( !item->IsBOARD_ITEM() )
|
|
|
|
continue;
|
|
|
|
|
|
|
|
BOARD_ITEM* boardItem = static_cast<BOARD_ITEM*>( item );
|
2025-05-19 21:36:46 +01:00
|
|
|
commit.Modify( boardItem, nullptr, RECURSE_MODE::RECURSE );
|
2024-12-31 18:12:20 +08:00
|
|
|
boardItem->Move( aMoveVec );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-05-10 23:55:03 +10:00
|
|
|
POSITION_RELATIVE_TOOL::POSITION_RELATIVE_TOOL() :
|
2019-05-12 12:49:58 +01:00
|
|
|
PCB_TOOL_BASE( "pcbnew.PositionRelative" ),
|
2021-07-19 19:56:05 -04:00
|
|
|
m_dialog( nullptr ),
|
2025-01-16 20:48:04 +08:00
|
|
|
m_selectionTool( nullptr ),
|
|
|
|
m_inInteractivePosition( false )
|
2017-05-10 23:55:03 +10:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void POSITION_RELATIVE_TOOL::Reset( RESET_REASON aReason )
|
|
|
|
{
|
|
|
|
if( aReason != RUN )
|
2019-12-05 05:43:55 -08:00
|
|
|
m_commit = std::make_unique<BOARD_COMMIT>( this );
|
2017-05-10 23:55:03 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool POSITION_RELATIVE_TOOL::Init()
|
|
|
|
{
|
|
|
|
// Find the selection tool, so they can cooperate
|
2020-12-16 13:31:32 +00:00
|
|
|
m_selectionTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
|
2017-05-10 23:55:03 +10:00
|
|
|
|
2018-08-22 19:05:40 +01:00
|
|
|
return m_selectionTool != nullptr;
|
2017-05-10 23:55:03 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int POSITION_RELATIVE_TOOL::PositionRelative( const TOOL_EVENT& aEvent )
|
|
|
|
{
|
2020-12-10 01:33:24 +00:00
|
|
|
PCB_BASE_FRAME* editFrame = getEditFrame<PCB_BASE_FRAME>();
|
2018-06-26 19:03:57 +01:00
|
|
|
|
2018-09-25 15:23:38 +01:00
|
|
|
const auto& selection = m_selectionTool->RequestSelection(
|
2025-08-20 14:18:06 +01:00
|
|
|
[]( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
|
|
|
|
{
|
|
|
|
sTool->FilterCollectorForHierarchy( aCollector, true );
|
|
|
|
sTool->FilterCollectorForMarkers( aCollector );
|
|
|
|
sTool->FilterCollectorForFreePads( aCollector, false );
|
|
|
|
sTool->FilterCollectorForLockedItems( aCollector );
|
|
|
|
} );
|
2018-06-26 19:03:57 +01:00
|
|
|
|
2018-09-25 15:23:38 +01:00
|
|
|
if( selection.Empty() )
|
2017-05-10 23:55:03 +10:00
|
|
|
return 0;
|
|
|
|
|
2018-08-22 19:05:40 +01:00
|
|
|
m_selection = selection;
|
2017-05-10 23:55:03 +10:00
|
|
|
|
2022-10-24 18:46:15 -04:00
|
|
|
// We prefer footprints, then pads, then anything else here.
|
|
|
|
EDA_ITEM* preferredItem = m_selection.GetTopLeftItem( true );
|
2022-08-22 22:32:42 +01:00
|
|
|
|
2022-10-24 18:46:15 -04:00
|
|
|
if( !preferredItem && m_selection.HasType( PCB_PAD_T ) )
|
2022-08-22 22:32:42 +01:00
|
|
|
{
|
2022-10-24 18:46:15 -04:00
|
|
|
PCB_SELECTION padsOnly = m_selection;
|
|
|
|
std::deque<EDA_ITEM*>& items = padsOnly.Items();
|
|
|
|
items.erase( std::remove_if( items.begin(), items.end(),
|
|
|
|
[]( const EDA_ITEM* aItem )
|
|
|
|
{
|
|
|
|
return aItem->Type() != PCB_PAD_T;
|
|
|
|
} ), items.end() );
|
|
|
|
|
|
|
|
preferredItem = padsOnly.GetTopLeftItem();
|
2022-08-22 22:32:42 +01:00
|
|
|
}
|
|
|
|
|
2022-10-24 18:46:15 -04:00
|
|
|
if( preferredItem )
|
|
|
|
m_selectionAnchor = preferredItem->GetPosition();
|
2021-01-17 14:25:43 -05:00
|
|
|
else
|
|
|
|
m_selectionAnchor = m_selection.GetTopLeftItem()->GetPosition();
|
|
|
|
|
2019-10-22 14:09:11 +02:00
|
|
|
// The dialog is not modal and not deleted between calls.
|
|
|
|
// It means some options can have changed since the last call.
|
|
|
|
// Therefore we need to rebuild it in case UI units have changed since the last call.
|
|
|
|
if( m_dialog && m_dialog->GetUserUnits() != editFrame->GetUserUnits() )
|
|
|
|
{
|
|
|
|
m_dialog->Destroy();
|
|
|
|
m_dialog = nullptr;
|
|
|
|
}
|
|
|
|
|
2018-08-22 19:05:40 +01:00
|
|
|
if( !m_dialog )
|
2022-11-17 00:49:26 +00:00
|
|
|
m_dialog = new DIALOG_POSITION_RELATIVE( editFrame );
|
2017-05-10 23:55:03 +10:00
|
|
|
|
2018-08-22 19:05:40 +01:00
|
|
|
m_dialog->Show( true );
|
2017-05-10 23:55:03 +10:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2024-10-29 12:59:27 +08:00
|
|
|
int POSITION_RELATIVE_TOOL::PositionRelativeInteractively( const TOOL_EVENT& aEvent )
|
|
|
|
{
|
2025-01-16 20:48:04 +08:00
|
|
|
if( m_inInteractivePosition )
|
|
|
|
return false;
|
|
|
|
|
|
|
|
REENTRANCY_GUARD guard( &m_inInteractivePosition );
|
|
|
|
|
2025-08-20 14:18:06 +01:00
|
|
|
// First, acquire the selection that we will be moving after we have the new offset vector.
|
|
|
|
const PCB_SELECTION& selection = m_selectionTool->RequestSelection(
|
|
|
|
[]( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
|
|
|
|
{
|
|
|
|
sTool->FilterCollectorForHierarchy( aCollector, true );
|
|
|
|
sTool->FilterCollectorForMarkers( aCollector );
|
|
|
|
sTool->FilterCollectorForFreePads( aCollector, false );
|
|
|
|
sTool->FilterCollectorForLockedItems( aCollector );
|
|
|
|
} );
|
2024-10-29 12:59:27 +08:00
|
|
|
|
2025-01-24 20:24:41 +08:00
|
|
|
if( selection.Empty() )
|
|
|
|
return 0;
|
|
|
|
|
2024-10-29 12:59:27 +08:00
|
|
|
if( m_isFootprintEditor && !frame()->GetModel() )
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if( frame()->IsCurrentTool( ACTIONS::measureTool ) )
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
auto& view = *getView();
|
|
|
|
auto& controls = *getViewControls();
|
|
|
|
|
|
|
|
frame()->PushTool( aEvent );
|
|
|
|
|
|
|
|
bool invertXAxis = displayOptions().m_DisplayInvertXAxis;
|
|
|
|
bool invertYAxis = displayOptions().m_DisplayInvertYAxis;
|
|
|
|
|
|
|
|
if( m_isFootprintEditor )
|
|
|
|
{
|
|
|
|
invertXAxis = frame()->GetFootprintEditorSettings()->m_DisplayInvertXAxis;
|
|
|
|
invertYAxis = frame()->GetFootprintEditorSettings()->m_DisplayInvertYAxis;
|
|
|
|
}
|
|
|
|
|
|
|
|
KIGFX::PREVIEW::TWO_POINT_GEOMETRY_MANAGER twoPtMgr;
|
|
|
|
PCB_GRID_HELPER grid( m_toolMgr, frame()->GetMagneticItemsSettings() );
|
|
|
|
bool originSet = false;
|
|
|
|
EDA_UNITS units = frame()->GetUserUnits();
|
|
|
|
KIGFX::PREVIEW::RULER_ITEM ruler( twoPtMgr, pcbIUScale, units, invertXAxis, invertYAxis );
|
|
|
|
STATUS_TEXT_POPUP statusPopup( frame() );
|
|
|
|
|
|
|
|
// Some colour to make it obviously not just a ruler
|
|
|
|
ruler.SetColor( view.GetPainter()->GetSettings()->GetLayerColor( LAYER_ANCHOR ) );
|
|
|
|
ruler.SetShowTicks( false );
|
2024-11-03 23:22:56 +08:00
|
|
|
ruler.SetShowEndArrowHead( true );
|
2024-10-29 12:59:27 +08:00
|
|
|
|
|
|
|
view.Add( &ruler );
|
|
|
|
view.SetVisible( &ruler, false );
|
|
|
|
|
|
|
|
auto setCursor =
|
|
|
|
[&]()
|
|
|
|
{
|
|
|
|
frame()->GetCanvas()->SetCurrentCursor( KICURSOR::MEASURE );
|
|
|
|
};
|
|
|
|
|
2025-04-01 14:35:48 +01:00
|
|
|
const auto setInitialMsg =
|
|
|
|
[&]()
|
|
|
|
{
|
|
|
|
statusPopup.SetText( _( "Select the reference point on the item to move." ) );
|
|
|
|
};
|
|
|
|
|
|
|
|
const auto setDragMsg =
|
|
|
|
[&]()
|
|
|
|
{
|
|
|
|
statusPopup.SetText( _( "Select the point to define the new offset from." ) );
|
|
|
|
};
|
|
|
|
|
|
|
|
const auto setPopupPosition =
|
|
|
|
[&]()
|
|
|
|
{
|
|
|
|
statusPopup.Move( KIPLATFORM::UI::GetMousePosition() + wxPoint( 20, -50 ) );
|
|
|
|
};
|
|
|
|
|
2024-10-29 12:59:27 +08:00
|
|
|
auto cleanup =
|
|
|
|
[&] ()
|
|
|
|
{
|
|
|
|
view.SetVisible( &ruler, false );
|
|
|
|
controls.SetAutoPan( false );
|
|
|
|
controls.CaptureCursor( false );
|
|
|
|
controls.ForceCursorPosition( false );
|
|
|
|
originSet = false;
|
2025-04-01 14:35:48 +01:00
|
|
|
setInitialMsg();
|
2024-10-29 12:59:27 +08:00
|
|
|
};
|
|
|
|
|
2025-04-01 14:35:48 +01:00
|
|
|
const auto applyVector =
|
|
|
|
[&]( const VECTOR2I& aMoveVec )
|
|
|
|
{
|
|
|
|
BOARD_COMMIT commit( frame() );
|
|
|
|
moveSelectionBy( selection, aMoveVec, commit );
|
|
|
|
commit.Push( _( "Set Relative Position Interactively" ) );
|
|
|
|
};
|
2024-10-29 12:59:27 +08:00
|
|
|
|
|
|
|
Activate();
|
|
|
|
// Must be done after Activate() so that it gets set into the correct context
|
|
|
|
controls.ShowCursor( true );
|
|
|
|
controls.SetAutoPan( false );
|
|
|
|
controls.CaptureCursor( false );
|
|
|
|
controls.ForceCursorPosition( false );
|
|
|
|
|
|
|
|
// Set initial cursor
|
|
|
|
setCursor();
|
|
|
|
|
|
|
|
setInitialMsg();
|
|
|
|
|
|
|
|
setPopupPosition();
|
|
|
|
statusPopup.Popup();
|
|
|
|
canvas()->SetStatusPopup( statusPopup.GetPanel() );
|
|
|
|
|
|
|
|
while( TOOL_EVENT* evt = Wait() )
|
|
|
|
{
|
|
|
|
setCursor();
|
|
|
|
grid.SetSnap( !evt->Modifier( MD_SHIFT ) );
|
|
|
|
grid.SetUseGrid( view.GetGAL()->GetGridSnapping() && !evt->DisableGridSnapping() );
|
|
|
|
VECTOR2I cursorPos = evt->HasPosition() ? evt->Position() : controls.GetMousePosition();
|
|
|
|
cursorPos = grid.BestSnapAnchor( cursorPos, nullptr );
|
|
|
|
controls.ForceCursorPosition( true, cursorPos );
|
|
|
|
setPopupPosition();
|
|
|
|
|
|
|
|
if( evt->IsCancelInteractive() )
|
|
|
|
{
|
|
|
|
if( originSet )
|
|
|
|
{
|
|
|
|
cleanup();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
frame()->PopTool( aEvent );
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if( evt->IsActivate() )
|
|
|
|
{
|
|
|
|
if( originSet )
|
|
|
|
cleanup();
|
|
|
|
|
|
|
|
frame()->PopTool( aEvent );
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
// click or drag starts
|
|
|
|
else if( !originSet && ( evt->IsDrag( BUT_LEFT ) || evt->IsClick( BUT_LEFT ) ) )
|
|
|
|
{
|
|
|
|
twoPtMgr.SetOrigin( cursorPos );
|
|
|
|
twoPtMgr.SetEnd( cursorPos );
|
|
|
|
|
|
|
|
setDragMsg();
|
|
|
|
|
|
|
|
controls.CaptureCursor( true );
|
|
|
|
controls.SetAutoPan( true );
|
|
|
|
|
|
|
|
originSet = true;
|
|
|
|
}
|
|
|
|
// second click or mouse up after drag ends
|
|
|
|
else if( originSet && ( evt->IsClick( BUT_LEFT ) || evt->IsMouseUp( BUT_LEFT ) ) )
|
|
|
|
{
|
2025-01-16 20:42:13 +08:00
|
|
|
// Hide the popup text so it doesn't get in the way
|
|
|
|
statusPopup.Hide();
|
|
|
|
|
2024-11-03 23:22:56 +08:00
|
|
|
// This is the forward vector from the ruler item
|
|
|
|
const VECTOR2I origVector = twoPtMgr.GetEnd() - twoPtMgr.GetOrigin();
|
2024-10-29 12:59:27 +08:00
|
|
|
VECTOR2I offsetVector = origVector;
|
2024-11-03 23:22:56 +08:00
|
|
|
// Start with the value of that vector in the dialog (will match the rule HUD)
|
2024-10-29 12:59:27 +08:00
|
|
|
DIALOG_SET_OFFSET dlg( *frame(), offsetVector, false );
|
|
|
|
|
|
|
|
int ret = dlg.ShowModal();
|
|
|
|
|
|
|
|
if( ret == wxID_OK )
|
|
|
|
{
|
2024-11-03 23:22:56 +08:00
|
|
|
const VECTOR2I move = origVector - offsetVector;
|
2024-10-29 12:59:27 +08:00
|
|
|
|
|
|
|
applyVector( move );
|
|
|
|
|
|
|
|
// Leave the arrow in place but update it
|
|
|
|
twoPtMgr.SetOrigin( twoPtMgr.GetOrigin() + move );
|
|
|
|
view.Update( &ruler, KIGFX::GEOMETRY );
|
|
|
|
canvas()->Refresh();
|
|
|
|
}
|
|
|
|
|
|
|
|
originSet = false;
|
|
|
|
|
|
|
|
setInitialMsg();
|
|
|
|
|
|
|
|
controls.SetAutoPan( false );
|
|
|
|
controls.CaptureCursor( false );
|
2025-01-16 20:42:13 +08:00
|
|
|
|
|
|
|
statusPopup.Popup();
|
2024-10-29 12:59:27 +08:00
|
|
|
}
|
|
|
|
// move or drag when origin set updates rules
|
|
|
|
else if( originSet && ( evt->IsMotion() || evt->IsDrag( BUT_LEFT ) ) )
|
|
|
|
{
|
2025-08-12 16:48:36 -07:00
|
|
|
auto snap = LEADER_MODE::DIRECT;
|
2024-10-29 12:59:27 +08:00
|
|
|
|
|
|
|
if( frame()->IsType( FRAME_PCB_EDITOR ) )
|
2025-08-12 16:48:36 -07:00
|
|
|
{
|
|
|
|
snap = GetAppSettings<PCBNEW_SETTINGS>( "pcbnew" )->m_AngleSnapMode;
|
|
|
|
}
|
2024-10-29 12:59:27 +08:00
|
|
|
else
|
2025-08-12 16:48:36 -07:00
|
|
|
{
|
|
|
|
snap = GetAppSettings<FOOTPRINT_EDITOR_SETTINGS>( "fpedit" )->m_AngleSnapMode;
|
|
|
|
}
|
2024-10-29 12:59:27 +08:00
|
|
|
|
2025-08-12 16:48:36 -07:00
|
|
|
twoPtMgr.SetAngleSnap( snap );
|
2024-10-29 12:59:27 +08:00
|
|
|
twoPtMgr.SetEnd( cursorPos );
|
|
|
|
|
|
|
|
view.SetVisible( &ruler, true );
|
|
|
|
view.Update( &ruler, KIGFX::GEOMETRY );
|
|
|
|
}
|
|
|
|
else if( evt->IsAction( &ACTIONS::updateUnits ) )
|
|
|
|
{
|
|
|
|
if( frame()->GetUserUnits() != units )
|
|
|
|
{
|
|
|
|
units = frame()->GetUserUnits();
|
|
|
|
ruler.SwitchUnits( units );
|
|
|
|
view.Update( &ruler, KIGFX::GEOMETRY );
|
|
|
|
canvas()->ForceRefresh();
|
|
|
|
}
|
|
|
|
|
|
|
|
evt->SetPassEvent();
|
|
|
|
}
|
|
|
|
else if( evt->IsAction( &ACTIONS::updatePreferences ) )
|
|
|
|
{
|
|
|
|
invertXAxis = displayOptions().m_DisplayInvertXAxis;
|
|
|
|
invertYAxis = displayOptions().m_DisplayInvertYAxis;
|
|
|
|
|
|
|
|
if( m_isFootprintEditor )
|
|
|
|
{
|
|
|
|
invertXAxis = frame()->GetFootprintEditorSettings()->m_DisplayInvertXAxis;
|
|
|
|
invertYAxis = frame()->GetFootprintEditorSettings()->m_DisplayInvertYAxis;
|
|
|
|
}
|
|
|
|
|
|
|
|
ruler.UpdateDir( invertXAxis, invertYAxis );
|
|
|
|
|
|
|
|
view.Update( &ruler, KIGFX::GEOMETRY );
|
|
|
|
canvas()->Refresh();
|
|
|
|
evt->SetPassEvent();
|
|
|
|
}
|
2025-01-16 20:21:37 +08:00
|
|
|
else if( !evt->IsMouseAction() )
|
|
|
|
{
|
|
|
|
// Often this will end up changing the items we just moved, so the ruler will be
|
|
|
|
// in the wrong place. Clear it away and the user can restart
|
|
|
|
twoPtMgr.Reset();
|
|
|
|
view.SetVisible( &ruler, false );
|
|
|
|
view.Update( &ruler, KIGFX::GEOMETRY );
|
|
|
|
|
|
|
|
evt->SetPassEvent();
|
|
|
|
}
|
2024-10-29 12:59:27 +08:00
|
|
|
else
|
|
|
|
{
|
|
|
|
evt->SetPassEvent();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
view.SetVisible( &ruler, false );
|
|
|
|
view.Remove( &ruler );
|
|
|
|
|
|
|
|
frame()->GetCanvas()->SetCurrentCursor( KICURSOR::ARROW );
|
|
|
|
controls.SetAutoPan( false );
|
|
|
|
controls.CaptureCursor( false );
|
|
|
|
controls.ForceCursorPosition( false );
|
|
|
|
|
|
|
|
canvas()->SetStatusPopup( nullptr );
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-12-29 16:30:11 -05:00
|
|
|
int POSITION_RELATIVE_TOOL::RelativeItemSelectionMove( const VECTOR2I& aPosAnchor,
|
|
|
|
const VECTOR2I& aTranslation )
|
2020-03-02 20:55:06 +00:00
|
|
|
{
|
2021-12-29 16:30:11 -05:00
|
|
|
VECTOR2I aggregateTranslation = aPosAnchor + aTranslation - GetSelectionAnchorPosition();
|
2024-12-31 18:12:20 +08:00
|
|
|
moveSelectionBy( m_selection, aggregateTranslation, *m_commit );
|
2017-06-14 08:14:57 +02:00
|
|
|
m_commit->Push( _( "Position Relative" ) );
|
2017-05-10 23:55:03 +10:00
|
|
|
|
2018-08-22 19:05:40 +01:00
|
|
|
if( m_selection.IsHover() )
|
2025-04-02 11:01:22 -04:00
|
|
|
m_toolMgr->RunAction( ACTIONS::selectionClear );
|
2017-05-10 23:55:03 +10:00
|
|
|
|
2019-05-08 19:56:03 +01:00
|
|
|
m_toolMgr->ProcessEvent( EVENTS::SelectedItemsModified );
|
2019-01-08 03:36:35 -08:00
|
|
|
|
|
|
|
canvas()->Refresh();
|
2017-05-10 23:55:03 +10:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-07-31 14:30:51 +02:00
|
|
|
void POSITION_RELATIVE_TOOL::setTransitions()
|
2017-05-10 23:55:03 +10:00
|
|
|
{
|
2024-10-29 12:59:27 +08:00
|
|
|
// clang-format off
|
|
|
|
Go( &POSITION_RELATIVE_TOOL::PositionRelative, PCB_ACTIONS::positionRelative.MakeEvent() );
|
|
|
|
Go( &POSITION_RELATIVE_TOOL::PositionRelativeInteractively, PCB_ACTIONS::positionRelativeInteractively.MakeEvent() );
|
|
|
|
// clang-format on
|
2017-05-10 23:55:03 +10:00
|
|
|
}
|