KiCad PCB EDA Suite
pl_selection_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 (C) 2019 CERN
5 * Copyright (C) 2019-2022 KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, you may find one here:
19 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
20 * or you may search the http://www.gnu.org website for the version 2 license,
21 * or you may write to the Free Software Foundation, Inc.,
22 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
23 */
24
25
26#include <view/view.h>
27#include <view/view_controls.h>
29#include <tool/tool_event.h>
30#include <tool/tool_manager.h>
31#include <tool/selection.h>
34#include <tools/pl_actions.h>
38#include <collector.h>
39#include <math/util.h> // for KiROUND
40
41#include "pl_editor_frame.h"
42
43
44#define HITTEST_THRESHOLD_PIXELS 3
45
46
48 SELECTION_TOOL( "plEditor.InteractiveSelection" ),
49 m_frame( nullptr )
50{
51}
52
53
55{
56 m_frame = getEditFrame<PL_EDITOR_FRAME>();
57
58 auto& menu = m_menu.GetMenu();
59
60 menu.AddSeparator( 200 );
65
66 menu.AddSeparator( 1000 );
68
69 m_disambiguateTimer.SetOwner( this );
70 Connect( wxEVT_TIMER, wxTimerEventHandler( PL_SELECTION_TOOL::onDisambiguationExpire ), nullptr, this );
71
72 return true;
73}
74
75
77{
78 if( aReason == MODEL_RELOAD )
79 m_frame = getEditFrame<PL_EDITOR_FRAME>();
80}
81
82
84{
85 // Main loop: keep receiving events
86 while( TOOL_EVENT* evt = Wait() )
87 {
88 // on left click, a selection is made, depending on modifiers ALT, SHIFT, CTRL:
89 setModifiersState( evt->Modifier( MD_SHIFT ), evt->Modifier( MD_CTRL ),
90 evt->Modifier( MD_ALT ) );
91
92 bool modifier_enabled = m_subtractive || m_additive || m_exclusive_or;
93
94 if( evt->IsMouseDown( BUT_LEFT ) )
95 {
96 // Avoid triggering when running under other tools
98
99 if( m_frame->ToolStackIsEmpty() && pt_tool && !pt_tool->HasPoint() )
100 {
102 m_disambiguateTimer.StartOnce( 500 );
103 }
104 }
105 // Single click? Select single object
106 else if( evt->IsClick( BUT_LEFT ) )
107 {
108 // If the timer has stopped, then we have already run the disambiguate routine
109 // and we don't want to register an extra click here
110 if( !m_disambiguateTimer.IsRunning() )
111 {
112 evt->SetPassEvent();
113 continue;
114 }
115
116 m_disambiguateTimer.Stop();
117 SelectPoint( evt->Position() );
118 }
119
120 // right click? if there is any object - show the context menu
121 else if( evt->IsClick( BUT_RIGHT ) )
122 {
123 m_disambiguateTimer.Stop();
124 bool selectionCancelled = false;
125
126 if( m_selection.Empty() )
127 {
128 SelectPoint( evt->Position(), &selectionCancelled );
129 m_selection.SetIsHover( true );
130 }
131
132 if( !selectionCancelled )
134 }
135
136 // double click? Display the properties window
137 else if( evt->IsDblClick( BUT_LEFT ) )
138 {
139 // No double-click actions currently defined
140 }
141
142 // drag with LMB? Select multiple objects (or at least draw a selection box) or drag them
143 else if( evt->IsDrag( BUT_LEFT ) )
144 {
145 m_disambiguateTimer.Stop();
146
147 if( modifier_enabled || m_selection.Empty() )
148 {
150 }
151 else
152 {
153 // Check if dragging has started within any of selected items bounding box
154 if( selectionContains( evt->Position() ) )
155 {
156 // Yes -> run the move tool and wait till it finishes
157 m_toolMgr->RunAction( "plEditor.InteractiveMove.move", true );
158 }
159 else
160 {
161 // No -> clear the selection list
163 }
164 }
165 }
166
167 // Middle double click? Do zoom to fit or zoom to objects
168 else if( evt->IsDblClick( BUT_MIDDLE ) )
169 {
171 }
172
173 else if( evt->IsCancelInteractive() )
174 {
175 m_disambiguateTimer.Stop();
177 }
178
179 else if( evt->Action() == TA_UNDO_REDO_PRE )
180 {
182 }
183
184 else
185 evt->SetPassEvent();
186
187
189 {
190 if( !modifier_enabled
191 && !m_selection.Empty()
193 && evt->HasPosition()
194 && selectionContains( evt->Position() ) )
195 {
197 }
198 else
199 {
200 if( m_additive )
202 else if( m_subtractive )
204 else if( m_exclusive_or )
206 else
208 }
209 }
210 }
211
212 return 0;
213}
214
215
217{
218 wxMouseState keyboardState = wxGetMouseState();
219
220 setModifiersState( keyboardState.ShiftDown(), keyboardState.ControlDown(),
221 keyboardState.AltDown() );
222
223 m_skip_heuristics = true;
225 m_skip_heuristics = false;
226
227 return 0;
228}
229
230
232{
233 return m_selection;
234}
235
236
237void PL_SELECTION_TOOL::SelectPoint( const VECTOR2I& aWhere, bool* aSelectionCancelledFlag )
238{
239 int threshold = KiROUND( getView()->ToWorld( HITTEST_THRESHOLD_PIXELS ) );
240
241 // locate items.
242 COLLECTOR collector;
243
244 for( DS_DATA_ITEM* dataItem : DS_DATA_MODEL::GetTheInstance().GetItems() )
245 {
246 for( DS_DRAW_ITEM_BASE* drawItem : dataItem->GetDrawItems() )
247 {
248 if( drawItem->HitTest( aWhere, threshold ) )
249 collector.Append( drawItem );
250 }
251 }
252
254
255 // Apply some ugly heuristics to avoid disambiguation menus whenever possible
256 if( collector.GetCount() > 1 && !m_skip_heuristics )
257 guessSelectionCandidates( collector, aWhere );
258
259 // If still more than one item we're going to have to ask the user.
260 if( collector.GetCount() > 1 )
261 {
262 doSelectionMenu( &collector );
263
264 if( collector.m_MenuCancelled )
265 {
266 if( aSelectionCancelledFlag )
267 *aSelectionCancelledFlag = true;
268
269 return;
270 }
271 }
272
273 bool anyAdded = false;
274 bool anySubtracted = false;
275
276
278 {
279 if( collector.GetCount() == 0 )
280 anySubtracted = true;
281
283 }
284
285 if( collector.GetCount() > 0 )
286 {
287 for( int i = 0; i < collector.GetCount(); ++i )
288 {
289 if( m_subtractive || ( m_exclusive_or && collector[i]->IsSelected() ) )
290 {
291 unselect( collector[i] );
292 anySubtracted = true;
293 }
294 else
295 {
296 select( collector[i] );
297 anyAdded = true;
298 }
299 }
300 }
301
302 if( anyAdded )
304
305 if( anySubtracted )
307}
308
309
311{
312 // There are certain conditions that can be handled automatically.
313
314 // Prefer an exact hit to a sloppy one
315 for( int i = 0; collector.GetCount() == 2 && i < 2; ++i )
316 {
317 EDA_ITEM* item = collector[ i ];
318 EDA_ITEM* other = collector[ ( i + 1 ) % 2 ];
319
320 if( item->HitTest( aPos, 0 ) && !other->HitTest( aPos, 0 ) )
321 collector.Transfer( other );
322 }
323}
324
325
327{
328 // If nothing is selected do a hover selection
329 if( m_selection.Empty() )
330 {
331 VECTOR2D cursorPos = getViewControls()->GetCursorPosition( true );
332
334 SelectPoint( cursorPos );
335 m_selection.SetIsHover( true );
336 }
337
338 return m_selection;
339}
340
341
343{
344 bool cancelled = false; // Was the tool cancelled while it was running?
345 m_multiple = true; // Multiple selection mode is active
346 KIGFX::VIEW* view = getView();
347
349 view->Add( &area );
350
351 while( TOOL_EVENT* evt = Wait() )
352 {
353 int width = area.GetEnd().x - area.GetOrigin().x;
354
355 /* Selection mode depends on direction of drag-selection:
356 * Left > Right : Select objects that are fully enclosed by selection
357 * Right > Left : Select objects that are crossed by selection
358 */
359 bool windowSelection = width >= 0 ? true : false;
360
363
364 if( evt->IsCancelInteractive() || evt->IsActivate() )
365 {
366 cancelled = true;
367 break;
368 }
369
370 if( evt->IsDrag( BUT_LEFT ) )
371 {
374
375 // Start drawing a selection box
376 area.SetOrigin( evt->DragOrigin() );
377 area.SetEnd( evt->Position() );
380 area.SetExclusiveOr( false );
381
382 view->SetVisible( &area, true );
383 view->Update( &area );
384 getViewControls()->SetAutoPan( true );
385 }
386
387 if( evt->IsMouseUp( BUT_LEFT ) )
388 {
389 getViewControls()->SetAutoPan( false );
390
391 // End drawing the selection box
392 view->SetVisible( &area, false );
393
394 int height = area.GetEnd().y - area.GetOrigin().y;
395
396 bool anyAdded = false;
397 bool anySubtracted = false;
398
399 // Construct a BOX2I to determine EDA_ITEM selection
400 BOX2I selectionRect( area.GetOrigin(), VECTOR2I( width, height ) );
401
402 selectionRect.Normalize();
403
404 for( DS_DATA_ITEM* dataItem : DS_DATA_MODEL::GetTheInstance().GetItems() )
405 {
406 for( DS_DRAW_ITEM_BASE* item : dataItem->GetDrawItems() )
407 {
408 if( item->HitTest( selectionRect, windowSelection ) )
409 {
410 if( m_subtractive || ( m_exclusive_or && item->IsSelected() ) )
411 {
412 unselect( item );
413 anySubtracted = true;
414 }
415 else
416 {
417 select( item );
418 anyAdded = true;
419 }
420 }
421 }
422 }
423
424 // Inform other potentially interested tools
425 if( anyAdded )
427
428 if( anySubtracted )
430
431 break; // Stop waiting for events
432 }
433 }
434
435 getViewControls()->SetAutoPan( false );
436
437 // Stop drawing the selection box
438 view->Remove( &area );
439 m_multiple = false; // Multiple selection mode is inactive
440
441 if( !cancelled )
443
444 return cancelled;
445}
446
447
449{
451 return 0;
452}
453
454
456{
458
459 for( DS_DATA_ITEM* dataItem : DS_DATA_MODEL::GetTheInstance().GetItems() )
460 {
461 for( DS_DRAW_ITEM_BASE* item : dataItem->GetDrawItems() )
462 {
463 if( item->IsSelected() )
464 select( item );
465 }
466 }
467}
468
469
471{
472 if( m_selection.Empty() )
473 return;
474
475 while( m_selection.GetSize() )
477
479
480 m_selection.SetIsHover( false );
482
483 // Inform other potentially interested tools
485}
486
487
489{
490 highlight( aItem, SELECTED, &m_selection );
491}
492
493
495{
496 unhighlight( aItem, SELECTED, &m_selection );
497}
498
499
500void PL_SELECTION_TOOL::highlight( EDA_ITEM* aItem, int aMode, SELECTION* aGroup )
501{
502 if( aMode == SELECTED )
503 aItem->SetSelected();
504 else if( aMode == BRIGHTENED )
505 aItem->SetBrightened();
506
507 if( aGroup )
508 aGroup->Add( aItem );
509
510 getView()->Update( aItem );
511}
512
513
514void PL_SELECTION_TOOL::unhighlight( EDA_ITEM* aItem, int aMode, SELECTION* aGroup )
515{
516 if( aMode == SELECTED )
517 aItem->ClearSelected();
518 else if( aMode == BRIGHTENED )
519 aItem->ClearBrightened();
520
521 if( aGroup )
522 aGroup->Remove( aItem );
523
524 getView()->Update( aItem );
525}
526
527
529{
530 const unsigned GRIP_MARGIN = 20;
531 VECTOR2I margin = getView()->ToWorld( VECTOR2I( GRIP_MARGIN, GRIP_MARGIN ), false );
532
533 // Check if the point is located within any of the currently selected items bounding boxes
534 for( EDA_ITEM* item : m_selection )
535 {
536 BOX2I itemBox = item->ViewBBox();
537 itemBox.Inflate( margin.x, margin.y ); // Give some margin for gripping an item
538
539 if( itemBox.Contains( aPoint ) )
540 return true;
541 }
542
543 return false;
544}
545
546
548{
550
553
559
561}
static TOOL_ACTION updateMenu
Definition: actions.h:171
static TOOL_ACTION zoomFitScreen
Definition: actions.h:98
BOX2< Vec > & Normalize()
Ensure that the height and width are positive.
Definition: box2.h:119
bool Contains(const Vec &aPoint) const
Definition: box2.h:141
BOX2< Vec > & Inflate(coord_type dx, coord_type dy)
Inflates the rectangle horizontally by dx and vertically by dy.
Definition: box2.h:506
An abstract class that will find and hold all the objects according to an inspection done by the Insp...
Definition: collector.h:48
void Transfer(int aIndex)
Move the item at aIndex (first position is 0) to the backup list.
Definition: collector.h:151
bool m_MenuCancelled
Definition: collector.h:237
int GetCount() const
Return the number of objects in the list.
Definition: collector.h:81
void Append(EDA_ITEM *item)
Add an item to the end of the list.
Definition: collector.h:99
void AddSeparator(int aOrder=ANY_ORDER)
Add a separator to the menu.
Drawing sheet structure type definitions.
Definition: ds_data_item.h:96
static DS_DATA_MODEL & GetTheInstance()
static function: returns the instance of DS_DATA_MODEL used in the application
Base class to handle basic graphic items.
Definition: ds_draw_item.h:59
void AddStandardSubMenus(TOOL_MENU &aMenu)
Construct a "basic" menu for a tool, containing only items that apply to all tools (e....
void SetCurrentCursor(KICURSOR aCursor)
Set the current cursor shape for this panel.
A base class for most all the KiCad significant classes used in schematics and boards.
Definition: eda_item.h:85
void ClearSelected()
Definition: eda_item.h:121
void SetSelected()
Definition: eda_item.h:118
void ClearBrightened()
Definition: eda_item.h:122
void SetBrightened()
Definition: eda_item.h:119
virtual bool HitTest(const VECTOR2I &aPosition, int aAccuracy=0) const
Test if aPosition is inside or on the boundary of this item.
Definition: eda_item.h:222
static const TOOL_EVENT DisambiguatePoint
Definition: actions.h:221
static const TOOL_EVENT ClearedEvent
Selected item had a property changed (except movement)
Definition: actions.h:208
static const TOOL_EVENT SelectedEvent
Definition: actions.h:206
static const TOOL_EVENT UnselectedEvent
Definition: actions.h:207
Represent a selection area (currently a rectangle) in a VIEW, drawn corner-to-corner between two poin...
void SetSubtractive(bool aSubtractive)
void SetAdditive(bool aAdditive)
void SetOrigin(const VECTOR2I &aOrigin)
void SetExclusiveOr(bool aExclusiveOr)
void SetEnd(const VECTOR2I &aEnd)
Set the current end of the rectangle (the corner that moves with the cursor.
VECTOR2D GetCursorPosition() const
Return the current cursor position in world coordinates.
virtual void SetAutoPan(bool aEnabled)
Turn on/off auto panning (this feature is used when there is a tool active (eg.
Hold a (potentially large) number of VIEW_ITEMs and renders them on a graphics device provided by the...
Definition: view.h:69
virtual void Add(VIEW_ITEM *aItem, int aDrawPriority=-1)
Add a VIEW_ITEM to the view.
Definition: view.cpp:316
virtual void Remove(VIEW_ITEM *aItem)
Remove a VIEW_ITEM from the view.
Definition: view.cpp:346
virtual void Update(const VIEW_ITEM *aItem, int aUpdateFlags) const
For dynamic VIEWs, inform the associated VIEW that the graphical representation of this item has chan...
Definition: view.cpp:1574
VECTOR2D ToWorld(const VECTOR2D &aCoord, bool aAbsolute=true) const
Converts a screen space point/vector to a point/vector in world space coordinates.
Definition: view.cpp:445
void SetVisible(VIEW_ITEM *aItem, bool aIsVisible=true)
Set the item visibility.
Definition: view.cpp:1512
static TOOL_ACTION clearSelection
Clear the current selection.
Definition: pl_actions.h:43
static TOOL_ACTION placeImage
Definition: pl_actions.h:59
static TOOL_ACTION selectionActivate
Activation of the selection tool.
Definition: pl_actions.h:40
static TOOL_ACTION selectionMenu
Run a selection menu to select from a list of items.
Definition: pl_actions.h:54
static TOOL_ACTION drawRectangle
Definition: pl_actions.h:60
static TOOL_ACTION removeItemFromSel
Definition: pl_actions.h:47
static TOOL_ACTION addItemsToSel
Select a list of items (specified as the event parameter)
Definition: pl_actions.h:50
static TOOL_ACTION removeItemsFromSel
Definition: pl_actions.h:51
static TOOL_ACTION addItemToSel
Select an item (specified as the event parameter).
Definition: pl_actions.h:46
static TOOL_ACTION placeText
Definition: pl_actions.h:58
static TOOL_ACTION drawLine
Definition: pl_actions.h:61
PL_DRAW_PANEL_GAL * GetCanvas() const override
Return a pointer to GAL-based canvas of given EDA draw frame.
Tool that displays edit points allowing to modify items by dragging the points.
bool HasPoint()
Indicate the cursor is over an edit point.
void select(EDA_ITEM *aItem) override
Takes necessary action mark an item as selected.
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.
PL_SELECTION m_selection
int disambiguateCursor(const TOOL_EVENT &aEvent)
Handle disambiguation actions including displaying the menu.
void SelectPoint(const VECTOR2I &aWhere, bool *aSelectionCancelledFlag=nullptr)
Select an item pointed by the parameter aWhere.
void guessSelectionCandidates(COLLECTOR &collector, const VECTOR2I &aWhere)
Apply heuristics to try and determine a single object when multiple are found under the cursor.
bool selectionContains(const VECTOR2I &aPoint) const
Set up handlers for various events.
void unselect(EDA_ITEM *aItem) override
Take necessary action mark an item as unselected.
bool selectMultiple()
Handle drawing a selection box that allows one to select many items at the same time.
PL_SELECTION & GetSelection()
Return the set of currently selected items.
PL_SELECTION & RequestSelection()
Return either an existing selection (filtered), or the selection at the current cursor if the existin...
void highlight(EDA_ITEM *aItem, int aHighlightMode, SELECTION *aGroup=nullptr) override
Highlight the item visually.
void unhighlight(EDA_ITEM *aItem, int aHighlightMode, SELECTION *aGroup=nullptr) override
Unhighlight the item visually.
PL_EDITOR_FRAME * m_frame
int Main(const TOOL_EVENT &aEvent)
The main loop.
void RebuildSelection()
Rebuild the selection from the flags in the view items.
void Reset(RESET_REASON aReason) override
Bring the tool to a known, initial state.
static bool Empty(const SELECTION &aSelection)
Test if there are no items selected.
int RemoveItemFromSel(const TOOL_EVENT &aEvent)
bool doSelectionMenu(COLLECTOR *aCollector)
wxTimer m_disambiguateTimer
int AddItemsToSel(const TOOL_EVENT &aEvent)
int AddItemToSel(const TOOL_EVENT &aEvent)
int UpdateMenu(const TOOL_EVENT &aEvent)
Update a menu's state based on the current selection.
void setModifiersState(bool aShiftState, bool aCtrlState, bool aAltState)
Set the configuration of m_additive, m_subtractive, m_exclusive_or, m_skip_heuristics from the state ...
VECTOR2I m_originalCursor
int SelectionMenu(const TOOL_EVENT &aEvent)
Show a popup menu to trim the COLLECTOR passed as aEvent's parameter down to a single item.
int RemoveItemsFromSel(const TOOL_EVENT &aEvent)
void onDisambiguationExpire(wxTimerEvent &aEvent)
Start the process to show our disambiguation menu once the user has kept the mouse down for the minim...
virtual void Add(EDA_ITEM *aItem)
Definition: selection.cpp:32
void SetIsHover(bool aIsHover)
Definition: selection.h:76
virtual void Remove(EDA_ITEM *aItem)
Definition: selection.cpp:50
virtual unsigned int GetSize() const override
Return the number of stored items.
Definition: selection.h:97
EDA_ITEM * Front() const
Definition: selection.h:206
virtual void Clear() override
Remove all the stored items from the group.
Definition: selection.h:90
void ClearReferencePoint()
Definition: selection.h:263
bool Empty() const
Checks if there is anything selected.
Definition: selection.h:107
MOUSE_DRAG_ACTION GetDragAction() const
Indicates whether a drag should draw a selection rectangle or drag selected (or unselected) objects.
Definition: tools_holder.h:147
bool ToolStackIsEmpty()
Definition: tools_holder.h:128
KIGFX::VIEW_CONTROLS * getViewControls() const
Return the instance of VIEW_CONTROLS object used in the application.
Definition: tool_base.cpp:42
TOOL_MANAGER * m_toolMgr
Definition: tool_base.h:214
KIGFX::VIEW * getView() const
Returns the instance of #VIEW object used in the application.
Definition: tool_base.cpp:36
RESET_REASON
Determine the reason of reset for a tool.
Definition: tool_base.h:78
@ MODEL_RELOAD
Model changes (required full reload)
Definition: tool_base.h:80
Generic, UI-independent tool event.
Definition: tool_event.h:156
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_MENU m_menu
The functions below are not yet implemented - their interface may change.
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.
bool ProcessEvent(const TOOL_EVENT &aEvent)
Propagate an event to tools that requested events of matching type(s).
bool RunAction(const std::string &aActionName, bool aNow=false, T aParam=NULL)
Run the specified action.
Definition: tool_manager.h:142
VECTOR2D GetMousePosition() const
CONDITIONAL_MENU & GetMenu()
Definition: tool_menu.cpp:44
void ShowContextMenu(SELECTION &aSelection)
Helper function to set and immediately show a CONDITIONAL_MENU in concert with the given SELECTION.
Definition: tool_menu.cpp:57
#define BRIGHTENED
item is drawn with a bright contour
#define SELECTED
Item was manually selected by the user.
#define HITTEST_THRESHOLD_PIXELS
@ TA_UNDO_REDO_PRE
Definition: tool_event.h:101
@ MD_ALT
Definition: tool_event.h:140
@ MD_CTRL
Definition: tool_event.h:139
@ MD_SHIFT
Definition: tool_event.h:138
@ BUT_MIDDLE
Definition: tool_event.h:129
@ BUT_LEFT
Definition: tool_event.h:127
@ BUT_RIGHT
Definition: tool_event.h:128
constexpr ret_type KiROUND(fp_type v)
Round a floating point number to an integer using "round halfway cases away from zero".
Definition: util.h:85
VECTOR2< int > VECTOR2I
Definition: vector2d.h:618