KiCad PCB EDA Suite
Loading...
Searching...
No Matches
position_relative_tool.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, you may find one here:
18 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
19 * or you may search the http://www.gnu.org website for the version 2 license,
20 * or you may write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 */
23
25
26#include <set>
27
28#include <board_commit.h>
29#include <collectors.h>
32#include <footprint.h>
35#include <kiplatform/ui.h>
36#include <pad.h>
37#include <pcb_group.h>
40#include <pcb_painter.h>
41#include <pgm_base.h>
43#include <render_settings.h>
45#include <status_popup.h>
46#include <tools/pcb_actions.h>
50#include <view/view_controls.h>
51
52
62static void moveSelectionBy( const PCB_SELECTION& aSelection, const VECTOR2I& aMoveVec,
63 BOARD_COMMIT& commit, bool aAllowFreePads )
64{
65 std::set<BOARD_ITEM*> moved;
66
67 for( EDA_ITEM* item : aSelection )
68 {
69 if( !item->IsBOARD_ITEM() )
70 continue;
71
72 BOARD_ITEM* boardItem = static_cast<BOARD_ITEM*>( item );
73
74 if( boardItem->Type() == PCB_PAD_T && !aAllowFreePads )
75 boardItem = boardItem->GetParent();
76
77 if( !moved.insert( boardItem ).second )
78 continue;
79
80 commit.Modify( boardItem, nullptr, RECURSE_MODE::RECURSE );
81 boardItem->Move( aMoveVec );
82 }
83}
84
85
87 PCB_TOOL_BASE( "pcbnew.PositionRelative" ),
88 m_dialog( nullptr ),
89 m_selectionTool( nullptr ),
91{
92}
93
94
96{
97 if( aReason != RUN )
98 m_commit = std::make_unique<BOARD_COMMIT>( this );
99}
100
101
103{
104 // Find the selection tool, so they can cooperate
106
107 return m_selectionTool != nullptr;
108}
109
110
112{
114
115 const auto& selection = m_selectionTool->RequestSelection(
116 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
117 {
118 sTool->FilterCollectorForHierarchy( aCollector, true );
119 sTool->FilterCollectorForMarkers( aCollector );
120 sTool->FilterCollectorForLockedItems( aCollector );
121 } );
122
123 if( selection.Empty() )
124 return 0;
125
127
128 // We prefer footprints, then pads, then anything else here.
129 EDA_ITEM* preferredItem = m_selection.GetTopLeftItem( true );
130
131 if( !preferredItem && m_selection.HasType( PCB_PAD_T ) )
132 {
133 PCB_SELECTION padsOnly = m_selection;
134 std::deque<EDA_ITEM*>& items = padsOnly.Items();
135 items.erase( std::remove_if( items.begin(), items.end(),
136 []( const EDA_ITEM* aItem )
137 {
138 return aItem->Type() != PCB_PAD_T;
139 } ), items.end() );
140
141 preferredItem = padsOnly.GetTopLeftItem();
142 }
143
144 if( preferredItem )
145 m_selectionAnchor = preferredItem->GetPosition();
146 else
147 m_selectionAnchor = m_selection.GetTopLeftItem()->GetPosition();
148
149 // The dialog is not modal and not deleted between calls.
150 // It means some options can have changed since the last call.
151 // Therefore we need to rebuild it in case UI units have changed since the last call.
152 if( m_dialog && m_dialog->GetUserUnits() != editFrame->GetUserUnits() )
153 {
154 m_dialog->Destroy();
155 m_dialog = nullptr;
156 }
157
158 if( !m_dialog )
159 m_dialog = new DIALOG_POSITION_RELATIVE( editFrame );
160
161 m_dialog->Show( true );
162
163 return 0;
164}
165
166
168{
170 return false;
171
173
174 // First, acquire the selection that we will be moving after we have the new offset vector.
175 const PCB_SELECTION& selection = m_selectionTool->RequestSelection(
176 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
177 {
178 sTool->FilterCollectorForHierarchy( aCollector, true );
179 sTool->FilterCollectorForMarkers( aCollector );
180 sTool->FilterCollectorForLockedItems( aCollector );
181 } );
182
183 if( selection.Empty() )
184 return 0;
185
186 if( m_isFootprintEditor && !frame()->GetModel() )
187 return 0;
188
189 if( frame()->IsCurrentTool( ACTIONS::measureTool ) )
190 return 0;
191
192 auto& view = *getView();
193 auto& controls = *getViewControls();
194
195 frame()->PushTool( aEvent );
196
197 bool invertXAxis = displayOptions().m_DisplayInvertXAxis;
198 bool invertYAxis = displayOptions().m_DisplayInvertYAxis;
199
201 {
202 invertXAxis = frame()->GetFootprintEditorSettings()->m_DisplayInvertXAxis;
203 invertYAxis = frame()->GetFootprintEditorSettings()->m_DisplayInvertYAxis;
204 }
205
207 PCB_GRID_HELPER grid( m_toolMgr, frame()->GetMagneticItemsSettings() );
208 bool originSet = false;
209 EDA_UNITS units = frame()->GetUserUnits();
210 KIGFX::PREVIEW::RULER_ITEM ruler( twoPtMgr, pcbIUScale, units, invertXAxis, invertYAxis );
211 STATUS_TEXT_POPUP statusPopup( frame() );
212
213 bool allowFreePads = m_isFootprintEditor
214 || ( frame()->GetPcbNewSettings()
215 && frame()->GetPcbNewSettings()->m_AllowFreePads );
216
217 // Some colour to make it obviously not just a ruler
218 ruler.SetColor( view.GetPainter()->GetSettings()->GetLayerColor( LAYER_ANCHOR ) );
219 ruler.SetShowTicks( false );
220 ruler.SetShowEndArrowHead( true );
221
222 view.Add( &ruler );
223 view.SetVisible( &ruler, false );
224
225 auto setCursor =
226 [&]()
227 {
228 frame()->GetCanvas()->SetCurrentCursor( KICURSOR::MEASURE );
229 };
230
231 const auto setInitialMsg =
232 [&]()
233 {
234 statusPopup.SetText( _( "Select the reference point on the item to move." ) );
235 };
236
237 const auto setDragMsg =
238 [&]()
239 {
240 statusPopup.SetText( _( "Select the point to define the new offset from." ) );
241 };
242
243 const auto setPopupPosition =
244 [&]()
245 {
246 statusPopup.Move( KIPLATFORM::UI::GetMousePosition() + wxPoint( 20, -50 ) );
247 };
248
249 auto cleanup =
250 [&] ()
251 {
252 view.SetVisible( &ruler, false );
253 controls.SetAutoPan( false );
254 controls.CaptureCursor( false );
255 controls.ForceCursorPosition( false );
256 originSet = false;
257 setInitialMsg();
258 };
259
260 const auto applyVector =
261 [&]( const VECTOR2I& aMoveVec )
262 {
263 BOARD_COMMIT commit( frame() );
264 moveSelectionBy( selection, aMoveVec, commit, allowFreePads );
265 commit.Push( _( "Set Relative Position Interactively" ) );
266 };
267
268 Activate();
269 // Must be done after Activate() so that it gets set into the correct context
270 controls.ShowCursor( true );
271 controls.SetAutoPan( false );
272 controls.CaptureCursor( false );
273 controls.ForceCursorPosition( false );
274
275 // Set initial cursor
276 setCursor();
277
278 setInitialMsg();
279
280 setPopupPosition();
281 statusPopup.Popup();
282 canvas()->SetStatusPopup( statusPopup.GetPanel() );
283
284 while( TOOL_EVENT* evt = Wait() )
285 {
286 setCursor();
287 grid.SetSnap( !evt->Modifier( MD_SHIFT ) );
288 grid.SetUseGrid( view.GetGAL()->GetGridSnapping() && !evt->DisableGridSnapping() );
289 VECTOR2I cursorPos = evt->HasPosition() ? evt->Position() : controls.GetMousePosition();
290 setPopupPosition();
291
292 if( !evt->IsActivate() && !evt->IsCancelInteractive() )
293 {
294 // If we are switching, the canvas may not be valid any more
295 cursorPos = grid.BestSnapAnchor( cursorPos, nullptr );
296 controls.ForceCursorPosition( true, cursorPos );
297 }
298 else
299 {
300 grid.FullReset();
301 }
302
303 if( evt->IsCancelInteractive() )
304 {
305 if( originSet )
306 {
307 cleanup();
308 }
309 else
310 {
311 frame()->PopTool( aEvent );
312 break;
313 }
314 }
315 else if( evt->IsActivate() )
316 {
317 if( originSet )
318 cleanup();
319
320 frame()->PopTool( aEvent );
321 break;
322 }
323 // click or drag starts
324 else if( !originSet && ( evt->IsDrag( BUT_LEFT ) || evt->IsClick( BUT_LEFT ) ) )
325 {
326 twoPtMgr.SetOrigin( cursorPos );
327 twoPtMgr.SetEnd( cursorPos );
328
329 setDragMsg();
330
331 controls.CaptureCursor( true );
332 controls.SetAutoPan( true );
333
334 originSet = true;
335 }
336 // second click or mouse up after drag ends
337 else if( originSet && ( evt->IsClick( BUT_LEFT ) || evt->IsMouseUp( BUT_LEFT ) ) )
338 {
339 // Hide the popup text so it doesn't get in the way
340 statusPopup.Hide();
341
342 // This is the forward vector from the ruler item
343 VECTOR2I offsetVector = twoPtMgr.GetEnd() - twoPtMgr.GetOrigin();
344 const VECTOR2I toReferencePtVector = twoPtMgr.GetOrigin() - twoPtMgr.GetEnd();
345
346 // Start with the value of that vector in the dialog (will match the rule HUD)
347 DIALOG_OFFSET_ITEM dlg( *frame(), offsetVector );
348
349 if( dlg.ShowModal() == wxID_OK )
350 {
351 const VECTOR2I move = toReferencePtVector + offsetVector;
352
353 applyVector( move );
354
355 // Leave the arrow in place but update it
356 twoPtMgr.SetEnd( twoPtMgr.GetOrigin() + offsetVector );
357 view.Update( &ruler, KIGFX::GEOMETRY );
358 canvas()->Refresh();
359 }
360
361 originSet = false;
362
363 setInitialMsg();
364
365 controls.SetAutoPan( false );
366 controls.CaptureCursor( false );
367
368 statusPopup.Popup();
369 }
370 // move or drag when origin set updates rules
371 else if( originSet && ( evt->IsMotion() || evt->IsDrag( BUT_LEFT ) ) )
372 {
373 auto snap = LEADER_MODE::DIRECT;
374
375 if( frame()->IsType( FRAME_PCB_EDITOR ) )
376 snap = GetAppSettings<PCBNEW_SETTINGS>( "pcbnew" )->m_AngleSnapMode;
377 else
378 snap = GetAppSettings<FOOTPRINT_EDITOR_SETTINGS>( "fpedit" )->m_AngleSnapMode;
379
380 twoPtMgr.SetAngleSnap( snap );
381 // The end is fixed; we must update the origin
382 twoPtMgr.SetOrigin( cursorPos );
383
384 view.SetVisible( &ruler, true );
385 view.Update( &ruler, KIGFX::GEOMETRY );
386 }
387 else if( evt->IsAction( &ACTIONS::updateUnits ) )
388 {
389 if( frame()->GetUserUnits() != units )
390 {
391 units = frame()->GetUserUnits();
392 ruler.SwitchUnits( units );
393 view.Update( &ruler, KIGFX::GEOMETRY );
394 canvas()->ForceRefresh();
395 }
396
397 evt->SetPassEvent();
398 }
399 else if( evt->IsAction( &ACTIONS::updatePreferences ) )
400 {
401 invertXAxis = displayOptions().m_DisplayInvertXAxis;
402 invertYAxis = displayOptions().m_DisplayInvertYAxis;
403
405 {
406 invertXAxis = frame()->GetFootprintEditorSettings()->m_DisplayInvertXAxis;
407 invertYAxis = frame()->GetFootprintEditorSettings()->m_DisplayInvertYAxis;
408 }
409
410 ruler.UpdateDir( invertXAxis, invertYAxis );
411
412 view.Update( &ruler, KIGFX::GEOMETRY );
413 canvas()->Refresh();
414 evt->SetPassEvent();
415 }
416 else if( !evt->IsMouseAction() )
417 {
418 // Often this will end up changing the items we just moved, so the ruler will be
419 // in the wrong place. Clear it away and the user can restart
420 twoPtMgr.Reset();
421 view.SetVisible( &ruler, false );
422 view.Update( &ruler, KIGFX::GEOMETRY );
423
424 evt->SetPassEvent();
425 }
426 else
427 {
428 evt->SetPassEvent();
429 }
430 }
431
432 view.SetVisible( &ruler, false );
433 view.Remove( &ruler );
434
435 frame()->GetCanvas()->SetCurrentCursor( KICURSOR::ARROW );
436 controls.SetAutoPan( false );
437 controls.CaptureCursor( false );
438 controls.ForceCursorPosition( false );
439
440 canvas()->SetStatusPopup( nullptr );
441 return 0;
442}
443
444
446 const VECTOR2I& aTranslation )
447{
448 VECTOR2I aggregateTranslation = aPosAnchor + aTranslation - GetSelectionAnchorPosition();
449
450 bool allowFreePads = m_isFootprintEditor
451 || ( frame()->GetPcbNewSettings()
452 && frame()->GetPcbNewSettings()->m_AllowFreePads );
453
454 moveSelectionBy( m_selection, aggregateTranslation, *m_commit, allowFreePads );
455 m_commit->Push( _( "Position Relative" ) );
456
457 if( m_selection.IsHover() )
459
461
462 canvas()->Refresh();
463 return 0;
464}
465
466
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:112
static TOOL_ACTION updatePreferences
Definition actions.h:276
static TOOL_ACTION updateUnits
Definition actions.h:207
static TOOL_ACTION measureTool
Definition actions.h:252
static TOOL_ACTION selectionClear
Clear the current selection.
Definition actions.h:224
virtual void Push(const wxString &aMessage=wxEmptyString, int aCommitFlags=0) override
Execute the changes.
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:84
virtual void Move(const VECTOR2I &aMoveVector)
Move this object.
Definition board_item.h:344
BOARD_ITEM_CONTAINER * GetParent() const
Definition board_item.h:215
COMMIT & Modify(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr, RECURSE_MODE aRecurse=RECURSE_MODE::NO_RECURSE)
Modify a given item in the model.
Definition commit.h:106
Dialog that invites the user to enter some kind of offset.
int ShowModal() override
void ForceRefresh()
Force a redraw.
virtual void Refresh(bool aEraseBackground=true, const wxRect *aRect=nullptr) override
void SetStatusPopup(wxWindow *aPopup)
A base class for most all the KiCad significant classes used in schematics and boards.
Definition eda_item.h:99
virtual VECTOR2I GetPosition() const
Definition eda_item.h:278
KICAD_T Type() const
Returns the type of object.
Definition eda_item.h:111
static const TOOL_EVENT SelectedItemsModified
Selected items were moved, this can be very high frequency on the canvas, use with care.
Definition actions.h:352
Used when the right click button is pressed, or when the select tool is in effect.
Definition collectors.h:207
A drawn ruler item for showing the distance between two points.
Definition ruler_item.h:46
void SetShowEndArrowHead(bool aShow)
Definition ruler_item.h:64
void SwitchUnits(EDA_UNITS aUnits)
Switch the ruler units.
Definition ruler_item.h:92
void UpdateDir(bool aFlipX, bool aFlipY)
Definition ruler_item.h:94
void SetColor(const COLOR4D &aColor)
Definition ruler_item.h:60
void SetShowTicks(bool aShow)
Definition ruler_item.h:62
Represent a very simple geometry manager for items that have a start and end point.
void SetOrigin(const VECTOR2I &aOrigin)
< Set the origin of the ruler (the fixed end)
void Reset()
Reset the manager to the initial state.
void SetEnd(const VECTOR2I &aEnd)
Set the current end of the rectangle (the end that moves with the cursor.
static TOOL_ACTION interactiveOffsetTool
static TOOL_ACTION positionRelative
Base PCB main window class for Pcbnew, Gerbview, and CvPcb footprint viewer.
The selection tool: currently supports:
void FilterCollectorForMarkers(GENERAL_COLLECTOR &aCollector) const
Drop any PCB_MARKERs from the collector.
void FilterCollectorForHierarchy(GENERAL_COLLECTOR &aCollector, bool aMultiselect) const
In general we don't want to select both a parent and any of it's children.
void FilterCollectorForLockedItems(GENERAL_COLLECTOR &aCollector)
In the PCB editor strip out any locked items unless the OverrideLocks checkbox is set.
EDA_ITEM * GetTopLeftItem(bool aFootprintsOnly=false) const override
T * frame() const
KIGFX::PCB_VIEW * view() const
KIGFX::VIEW_CONTROLS * controls() const
PCB_TOOL_BASE(TOOL_ID aId, const std::string &aName)
Constructor.
PCB_DRAW_PANEL_GAL * canvas() const
PCBNEW_SETTINGS::DISPLAY_OPTIONS & displayOptions() const
const PCB_SELECTION & selection() const
int RelativeItemSelectionMove(const VECTOR2I &anchor, const VECTOR2I &translation)
Position the m_position_relative_selection selection relative to anchor position using the given tran...
int PositionRelative(const TOOL_EVENT &aEvent)
Invoke a dialog box to allow positioning of the item relative to another by an exact amount.
void setTransitions() override
This method is meant to be overridden in order to specify handlers for events.
bool Init() override
Init() is called once upon a registration of the tool.
DIALOG_POSITION_RELATIVE * m_dialog
std::unique_ptr< BOARD_COMMIT > m_commit
VECTOR2I GetSelectionAnchorPosition() const
Return the position of the selected item(s)
void Reset(RESET_REASON aReason) override
Bring the tool to a known, initial state.
int InteractiveOffset(const TOOL_EVENT &aEvent)
Draw a line connecting two points and allow the user to enter what it should be.
PCB_SELECTION_TOOL * m_selectionTool
std::deque< EDA_ITEM * > & Items()
Definition selection.h:182
wxWindow * GetPanel()
virtual void Popup(wxWindow *aFocus=nullptr)
virtual void Move(const wxPoint &aWhere)
Extension of STATUS_POPUP for displaying a single line text.
void SetText(const wxString &aText)
Display a text.
T * getEditFrame() const
Return the application window object, casted to requested user type.
Definition tool_base.h:186
KIGFX::VIEW_CONTROLS * getViewControls() const
Return the instance of VIEW_CONTROLS object used in the application.
Definition tool_base.cpp:44
TOOL_MANAGER * m_toolMgr
Definition tool_base.h:220
KIGFX::VIEW * getView() const
Returns the instance of #VIEW object used in the application.
Definition tool_base.cpp:38
RESET_REASON
Determine the reason of reset for a tool.
Definition tool_base.h:78
@ RUN
Tool is invoked after being inactive.
Definition tool_base.h:79
Generic, UI-independent tool event.
Definition tool_event.h:171
void Go(int(T::*aStateFunc)(const TOOL_EVENT &), const TOOL_EVENT_LIST &aConditions=TOOL_EVENT(TC_ANY, TA_ANY))
Define which state (aStateFunc) to go when a certain event arrives (aConditions).
TOOL_EVENT * Wait(const TOOL_EVENT_LIST &aEventList=TOOL_EVENT(TC_ANY, TA_ANY))
Suspend execution of the tool until an event specified in aEventList arrives.
void Activate()
Run the tool.
EDA_UNITS GetUserUnits() const
@ MEASURE
Definition cursors.h:68
@ ARROW
Definition cursors.h:46
#define _(s)
@ RECURSE
Definition eda_item.h:52
EDA_UNITS
Definition eda_units.h:48
@ FRAME_PCB_EDITOR
Definition frame_type.h:42
@ DIRECT
Unconstrained point-to-point.
@ LAYER_ANCHOR
Anchor of items having an anchor point (texts, footprints).
Definition layer_ids.h:248
@ GEOMETRY
Position or shape has changed.
Definition view_item.h:55
wxPoint GetMousePosition()
Returns the mouse position in screen coordinates.
Definition wxgtk/ui.cpp:721
Class to handle a set of BOARD_ITEMs.
int GetUserUnits()
Return the currently selected user unit value for the interface.
see class PGM_BASE
static void moveSelectionBy(const PCB_SELECTION &aSelection, const VECTOR2I &aMoveVec, BOARD_COMMIT &commit, bool aAllowFreePads)
Move each item in the selection by the given vector.
T * GetAppSettings(const char *aFilename)
bool moved
@ MD_SHIFT
Definition tool_event.h:143
@ BUT_LEFT
Definition tool_event.h:132
@ PCB_PAD_T
class PAD, a pad in a footprint
Definition typeinfo.h:87
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:695