7
mirror of https://gitlab.com/kicad/code/kicad.git synced 2024-11-22 10:25:00 +00:00
kicad/pcbnew/tools/position_relative_tool.cpp
John Beard 11ac6ea976 Position interactive: use the forward vector value
On reflection, the forward vector makes more sense, because
the value in the edit box is then the same as the vector the
user just drew with the ruler.
2024-11-04 20:41:20 +08:00

437 lines
14 KiB
C++

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2017-2022 KiCad Developers, see AUTHORS.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 "tools/position_relative_tool.h"
#include <board_commit.h>
#include <collectors.h>
#include <dialogs/dialog_position_relative.h>
#include <dialogs/dialog_set_offset.h>
#include <footprint.h>
#include <footprint_editor_settings.h>
#include <gal/graphics_abstraction_layer.h>
#include <kiplatform/ui.h>
#include <pad.h>
#include <pcb_group.h>
#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>
POSITION_RELATIVE_TOOL::POSITION_RELATIVE_TOOL() :
PCB_TOOL_BASE( "pcbnew.PositionRelative" ),
m_dialog( nullptr ),
m_selectionTool( nullptr )
{
}
void POSITION_RELATIVE_TOOL::Reset( RESET_REASON aReason )
{
if( aReason != RUN )
m_commit = std::make_unique<BOARD_COMMIT>( this );
}
bool POSITION_RELATIVE_TOOL::Init()
{
// Find the selection tool, so they can cooperate
m_selectionTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
return m_selectionTool != nullptr;
}
int POSITION_RELATIVE_TOOL::PositionRelative( const TOOL_EVENT& aEvent )
{
PCB_BASE_FRAME* editFrame = getEditFrame<PCB_BASE_FRAME>();
const auto& selection = m_selectionTool->RequestSelection(
[]( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
{
sTool->FilterCollectorForHierarchy( aCollector, true );
sTool->FilterCollectorForMarkers( aCollector );
},
!m_isFootprintEditor /* prompt user regarding locked items */ );
if( selection.Empty() )
return 0;
m_selection = selection;
// We prefer footprints, then pads, then anything else here.
EDA_ITEM* preferredItem = m_selection.GetTopLeftItem( true );
if( !preferredItem && m_selection.HasType( PCB_PAD_T ) )
{
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();
}
if( preferredItem )
m_selectionAnchor = preferredItem->GetPosition();
else
m_selectionAnchor = m_selection.GetTopLeftItem()->GetPosition();
// 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;
}
if( !m_dialog )
m_dialog = new DIALOG_POSITION_RELATIVE( editFrame );
m_dialog->Show( true );
return 0;
}
int POSITION_RELATIVE_TOOL::PositionRelativeInteractively( const TOOL_EVENT& aEvent )
{
// First, acquire the selection that we will be moving after
// we have the new offset vector.
const auto& selection = m_selectionTool->RequestSelection(
[]( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
{
sTool->FilterCollectorForHierarchy( aCollector, true );
sTool->FilterCollectorForMarkers( aCollector );
},
!m_isFootprintEditor /* prompt user regarding locked items */ );
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 );
ruler.SetShowEndArrowHead( true );
view.Add( &ruler );
view.SetVisible( &ruler, false );
auto setCursor =
[&]()
{
frame()->GetCanvas()->SetCurrentCursor( KICURSOR::MEASURE );
};
auto cleanup =
[&] ()
{
view.SetVisible( &ruler, false );
controls.SetAutoPan( false );
controls.CaptureCursor( false );
controls.ForceCursorPosition( false );
originSet = false;
};
const auto applyVector = [&]( const VECTOR2I& aMoveVec )
{
BOARD_COMMIT commit( frame() );
for( EDA_ITEM* item : selection )
{
if( !item->IsBOARD_ITEM() )
continue;
BOARD_ITEM* boardItem = static_cast<BOARD_ITEM*>( item );
// Don't move a pad by itself unless editing the footprint
if( boardItem->Type() == PCB_PAD_T
&& !frame()->GetPcbNewSettings()->m_AllowFreePads
&& frame()->IsType( FRAME_PCB_EDITOR ) )
continue;
commit.Modify( boardItem );
boardItem->Move( aMoveVec );
}
commit.Push( _( "Set Relative Position Interactively" ) );
};
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();
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 ) );
};
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 ) ) )
{
// This is the forward vector from the ruler item
const VECTOR2I origVector = twoPtMgr.GetEnd() - twoPtMgr.GetOrigin();
VECTOR2I offsetVector = origVector;
// Start with the value of that vector in the dialog (will match the rule HUD)
DIALOG_SET_OFFSET dlg( *frame(), offsetVector, false );
int ret = dlg.ShowModal();
if( ret == wxID_OK )
{
const VECTOR2I move = origVector - offsetVector;
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 );
}
// move or drag when origin set updates rules
else if( originSet && ( evt->IsMotion() || evt->IsDrag( BUT_LEFT ) ) )
{
SETTINGS_MANAGER& mgr = Pgm().GetSettingsManager();
bool force45Deg;
if( frame()->IsType( FRAME_PCB_EDITOR ) )
force45Deg = mgr.GetAppSettings<PCBNEW_SETTINGS>()->m_Use45DegreeLimit;
else
force45Deg = mgr.GetAppSettings<FOOTPRINT_EDITOR_SETTINGS>()->m_Use45Limit;
twoPtMgr.SetAngleSnap( force45Deg );
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();
}
else if( evt->IsClick( BUT_RIGHT ) )
{
// TODO: This does not work
PCB_SELECTION dummy;
PCB_PICKER_TOOL* picker = m_toolMgr->GetTool<PCB_PICKER_TOOL>();
picker->GetToolMenu().ShowContextMenu( dummy );
}
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;
}
int POSITION_RELATIVE_TOOL::RelativeItemSelectionMove( const VECTOR2I& aPosAnchor,
const VECTOR2I& aTranslation )
{
VECTOR2I aggregateTranslation = aPosAnchor + aTranslation - GetSelectionAnchorPosition();
for( EDA_ITEM* item : m_selection )
{
if( !item->IsBOARD_ITEM() )
continue;
BOARD_ITEM* boardItem = static_cast<BOARD_ITEM*>( item );
// Don't move a pad by itself unless editing the footprint
if( boardItem->Type() == PCB_PAD_T
&& !frame()->GetPcbNewSettings()->m_AllowFreePads
&& frame()->IsType( FRAME_PCB_EDITOR ) )
{
boardItem = boardItem->GetParent();
}
m_commit->Modify( boardItem );
boardItem->Move( aggregateTranslation );
}
m_commit->Push( _( "Position Relative" ) );
if( m_selection.IsHover() )
m_toolMgr->RunAction( PCB_ACTIONS::selectionClear );
m_toolMgr->ProcessEvent( EVENTS::SelectedItemsModified );
canvas()->Refresh();
return 0;
}
void POSITION_RELATIVE_TOOL::setTransitions()
{
// clang-format off
Go( &POSITION_RELATIVE_TOOL::PositionRelative, PCB_ACTIONS::positionRelative.MakeEvent() );
Go( &POSITION_RELATIVE_TOOL::PositionRelativeInteractively, PCB_ACTIONS::positionRelativeInteractively.MakeEvent() );
// clang-format on
}