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) 2021 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 <bitmaps.h>
27 #include <view/view.h>
28 #include <view/view_controls.h>
29 #include <preview_items/selection_area.h>
30 #include <tool/tool_event.h>
31 #include <tool/tool_manager.h>
32 #include <tool/selection.h>
33 #include <tools/pl_point_editor.h>
34 #include <tools/pl_selection_tool.h>
35 #include <tools/pl_actions.h>
36 #include <drawing_sheet/ds_data_item.h>
37 #include <drawing_sheet/ds_data_model.h>
38 #include <drawing_sheet/ds_draw_item.h>
39 #include <collector.h>
40 #include <math/util.h> // for KiROUND
41
42 #include "pl_editor_frame.h"
43
44 /**
45 * The maximum number of items in the clarify selection context menu. The current
46 * setting of 40 is arbitrary.
47 */
48 #define MAX_SELECT_ITEM_IDS 40
49 #define HITTEST_THRESHOLD_PIXELS 3
50
51
PL_SELECTION_TOOL()52 PL_SELECTION_TOOL::PL_SELECTION_TOOL() :
53 TOOL_INTERACTIVE( "plEditor.InteractiveSelection" ),
54 m_frame( nullptr )
55 {
56 }
57
58
Init()59 bool PL_SELECTION_TOOL::Init()
60 {
61 m_frame = getEditFrame<PL_EDITOR_FRAME>();
62
63 auto& menu = m_menu.GetMenu();
64
65 menu.AddSeparator( 200 );
66 menu.AddItem( PL_ACTIONS::drawLine, SELECTION_CONDITIONS::Empty, 200 );
67 menu.AddItem( PL_ACTIONS::drawRectangle, SELECTION_CONDITIONS::Empty, 200 );
68 menu.AddItem( PL_ACTIONS::placeText, SELECTION_CONDITIONS::Empty, 200 );
69 menu.AddItem( PL_ACTIONS::placeImage, SELECTION_CONDITIONS::Empty, 200 );
70
71 menu.AddSeparator( 1000 );
72 m_frame->AddStandardSubMenus( m_menu );
73
74 m_disambiguateTimer.SetOwner( this );
75 Connect( wxEVT_TIMER, wxTimerEventHandler( PL_SELECTION_TOOL::onDisambiguationExpire ), nullptr, this );
76
77 return true;
78 }
79
80
Reset(RESET_REASON aReason)81 void PL_SELECTION_TOOL::Reset( RESET_REASON aReason )
82 {
83 if( aReason == MODEL_RELOAD )
84 m_frame = getEditFrame<PL_EDITOR_FRAME>();
85 }
86
87
UpdateMenu(const TOOL_EVENT & aEvent)88 int PL_SELECTION_TOOL::UpdateMenu( const TOOL_EVENT& aEvent )
89 {
90 ACTION_MENU* actionMenu = aEvent.Parameter<ACTION_MENU*>();
91 CONDITIONAL_MENU* conditionalMenu = dynamic_cast<CONDITIONAL_MENU*>( actionMenu );
92
93 if( conditionalMenu )
94 conditionalMenu->Evaluate( m_selection );
95
96 if( actionMenu )
97 actionMenu->UpdateAll();
98
99 return 0;
100 }
101
102
Main(const TOOL_EVENT & aEvent)103 int PL_SELECTION_TOOL::Main( const TOOL_EVENT& aEvent )
104 {
105 // Main loop: keep receiving events
106 while( TOOL_EVENT* evt = Wait() )
107 {
108 // on left click, a selection is made, depending on modifiers ALT, SHIFT, CTRL:
109 setModifiersState( evt->Modifier( MD_SHIFT ), evt->Modifier( MD_CTRL ),
110 evt->Modifier( MD_ALT ) );
111
112 bool modifier_enabled = m_subtractive || m_additive || m_exclusive_or;
113
114 if( evt->IsMouseDown( BUT_LEFT ) )
115 {
116 // Avoid triggering when running under other tools
117 PL_POINT_EDITOR *pt_tool = m_toolMgr->GetTool<PL_POINT_EDITOR>();
118
119 if( m_frame->ToolStackIsEmpty() && pt_tool && !pt_tool->HasPoint() )
120 {
121 m_originalCursor = m_toolMgr->GetMousePosition();
122 m_disambiguateTimer.StartOnce( 500 );
123 }
124 }
125 // Single click? Select single object
126 else if( evt->IsClick( BUT_LEFT ) )
127 {
128 // If the timer has stopped, then we have already run the disambiguate routine
129 // and we don't want to register an extra click here
130 if( !m_disambiguateTimer.IsRunning() )
131 {
132 evt->SetPassEvent();
133 continue;
134 }
135
136 m_disambiguateTimer.Stop();
137 SelectPoint( evt->Position() );
138 }
139
140 // right click? if there is any object - show the context menu
141 else if( evt->IsClick( BUT_RIGHT ) )
142 {
143 m_disambiguateTimer.Stop();
144 bool selectionCancelled = false;
145
146 if( m_selection.Empty() )
147 {
148 SelectPoint( evt->Position(), &selectionCancelled );
149 m_selection.SetIsHover( true );
150 }
151
152 if( !selectionCancelled )
153 m_menu.ShowContextMenu( m_selection );
154 }
155
156 // double click? Display the properties window
157 else if( evt->IsDblClick( BUT_LEFT ) )
158 {
159 // No double-click actions currently defined
160 }
161
162 // drag with LMB? Select multiple objects (or at least draw a selection box) or drag them
163 else if( evt->IsDrag( BUT_LEFT ) )
164 {
165 m_disambiguateTimer.Stop();
166
167 if( modifier_enabled || m_selection.Empty() )
168 {
169 selectMultiple();
170 }
171 else
172 {
173 // Check if dragging has started within any of selected items bounding box
174 if( selectionContains( evt->Position() ) )
175 {
176 // Yes -> run the move tool and wait till it finishes
177 m_toolMgr->RunAction( "plEditor.InteractiveMove.move", true );
178 }
179 else
180 {
181 // No -> clear the selection list
182 ClearSelection();
183 }
184 }
185 }
186
187 // Middle double click? Do zoom to fit or zoom to objects
188 else if( evt->IsDblClick( BUT_MIDDLE ) )
189 {
190 m_toolMgr->RunAction( ACTIONS::zoomFitScreen, true );
191 }
192
193 else if( evt->IsCancelInteractive() )
194 {
195 m_disambiguateTimer.Stop();
196 ClearSelection();
197 }
198
199 else if( evt->Action() == TA_UNDO_REDO_PRE )
200 {
201 ClearSelection();
202 }
203
204 else
205 evt->SetPassEvent();
206
207
208 if( m_frame->ToolStackIsEmpty() )
209 {
210 if( !modifier_enabled
211 && !m_selection.Empty()
212 && m_frame->GetDragAction() == MOUSE_DRAG_ACTION::DRAG_SELECTED
213 && evt->HasPosition()
214 && selectionContains( evt->Position() ) )
215 {
216 m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::MOVING );
217 }
218 else
219 {
220 if( m_additive )
221 m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::ADD );
222 else if( m_subtractive )
223 m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::SUBTRACT );
224 else if( m_exclusive_or )
225 m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::XOR );
226 else
227 m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::ARROW );
228 }
229 }
230 }
231
232 return 0;
233 }
234
235
disambiguateCursor(const TOOL_EVENT & aEvent)236 int PL_SELECTION_TOOL::disambiguateCursor( const TOOL_EVENT& aEvent )
237 {
238 m_skip_heuristics = true;
239 SelectPoint( m_originalCursor, &m_canceledMenu );
240 m_skip_heuristics = false;
241
242 return 0;
243 }
244
245
onDisambiguationExpire(wxTimerEvent & aEvent)246 void PL_SELECTION_TOOL::onDisambiguationExpire( wxTimerEvent& aEvent )
247 {
248 m_toolMgr->ProcessEvent( EVENTS::DisambiguatePoint );
249 }
250
251
GetSelection()252 PL_SELECTION& PL_SELECTION_TOOL::GetSelection()
253 {
254 return m_selection;
255 }
256
257
SelectPoint(const VECTOR2I & aWhere,bool * aSelectionCancelledFlag)258 void PL_SELECTION_TOOL::SelectPoint( const VECTOR2I& aWhere, bool* aSelectionCancelledFlag )
259 {
260 int threshold = KiROUND( getView()->ToWorld( HITTEST_THRESHOLD_PIXELS ) );
261
262 // locate items.
263 COLLECTOR collector;
264
265 for( DS_DATA_ITEM* dataItem : DS_DATA_MODEL::GetTheInstance().GetItems() )
266 {
267 for( DS_DRAW_ITEM_BASE* drawItem : dataItem->GetDrawItems() )
268 {
269 if( drawItem->HitTest( (wxPoint) aWhere, threshold ) )
270 collector.Append( drawItem );
271 }
272 }
273
274 m_selection.ClearReferencePoint();
275
276 // Apply some ugly heuristics to avoid disambiguation menus whenever possible
277 if( collector.GetCount() > 1 && !m_skip_heuristics )
278 {
279 guessSelectionCandidates( collector, aWhere );
280 }
281
282 // If still more than one item we're going to have to ask the user.
283 if( collector.GetCount() > 1 )
284 {
285 doSelectionMenu( &collector );
286
287 if( collector.m_MenuCancelled )
288 {
289 if( aSelectionCancelledFlag )
290 *aSelectionCancelledFlag = true;
291
292 return;
293 }
294 }
295
296 bool anyAdded = false;
297 bool anySubtracted = false;
298
299
300 if( !m_additive && !m_subtractive && !m_exclusive_or )
301 {
302 if( collector.GetCount() == 0 )
303 anySubtracted = true;
304
305 ClearSelection();
306 }
307
308 if( collector.GetCount() > 0 )
309 {
310 for( int i = 0; i < collector.GetCount(); ++i )
311 {
312 if( m_subtractive || ( m_exclusive_or && collector[i]->IsSelected() ) )
313 {
314 unselect( collector[i] );
315 anySubtracted = true;
316 }
317 else
318 {
319 select( collector[i] );
320 anyAdded = true;
321 }
322 }
323 }
324
325 if( anyAdded )
326 m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
327
328 if( anySubtracted )
329 m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent );
330 }
331
332
guessSelectionCandidates(COLLECTOR & collector,const VECTOR2I & aPos)333 void PL_SELECTION_TOOL::guessSelectionCandidates( COLLECTOR& collector, const VECTOR2I& aPos )
334 {
335 // There are certain conditions that can be handled automatically.
336
337 // Prefer an exact hit to a sloppy one
338 for( int i = 0; collector.GetCount() == 2 && i < 2; ++i )
339 {
340 EDA_ITEM* item = collector[ i ];
341 EDA_ITEM* other = collector[ ( i + 1 ) % 2 ];
342
343 if( item->HitTest( (wxPoint) aPos, 0 ) && !other->HitTest( (wxPoint) aPos, 0 ) )
344 collector.Transfer( other );
345 }
346 }
347
348
RequestSelection()349 PL_SELECTION& PL_SELECTION_TOOL::RequestSelection()
350 {
351 // If nothing is selected do a hover selection
352 if( m_selection.Empty() )
353 {
354 VECTOR2D cursorPos = getViewControls()->GetCursorPosition( true );
355
356 ClearSelection();
357 SelectPoint( cursorPos );
358 m_selection.SetIsHover( true );
359 }
360
361 return m_selection;
362 }
363
364
selectMultiple()365 bool PL_SELECTION_TOOL::selectMultiple()
366 {
367 bool cancelled = false; // Was the tool cancelled while it was running?
368 m_multiple = true; // Multiple selection mode is active
369 KIGFX::VIEW* view = getView();
370
371 KIGFX::PREVIEW::SELECTION_AREA area;
372 view->Add( &area );
373
374 while( TOOL_EVENT* evt = Wait() )
375 {
376 int width = area.GetEnd().x - area.GetOrigin().x;
377
378 /* Selection mode depends on direction of drag-selection:
379 * Left > Right : Select objects that are fully enclosed by selection
380 * Right > Left : Select objects that are crossed by selection
381 */
382 bool windowSelection = width >= 0 ? true : false;
383
384 m_frame->GetCanvas()->SetCurrentCursor(
385 windowSelection ? KICURSOR::SELECT_WINDOW : KICURSOR::SELECT_LASSO );
386
387 if( evt->IsCancelInteractive() || evt->IsActivate() )
388 {
389 cancelled = true;
390 break;
391 }
392
393 if( evt->IsDrag( BUT_LEFT ) )
394 {
395 if( !m_drag_additive && !m_drag_subtractive )
396 ClearSelection();
397
398 // Start drawing a selection box
399 area.SetOrigin( evt->DragOrigin() );
400 area.SetEnd( evt->Position() );
401 area.SetAdditive( m_drag_additive );
402 area.SetSubtractive( m_drag_subtractive );
403 area.SetExclusiveOr( false );
404
405 view->SetVisible( &area, true );
406 view->Update( &area );
407 getViewControls()->SetAutoPan( true );
408 }
409
410 if( evt->IsMouseUp( BUT_LEFT ) )
411 {
412 getViewControls()->SetAutoPan( false );
413
414 // End drawing the selection box
415 view->SetVisible( &area, false );
416
417 int height = area.GetEnd().y - area.GetOrigin().y;
418
419 bool anyAdded = false;
420 bool anySubtracted = false;
421
422 // Construct an EDA_RECT to determine EDA_ITEM selection
423 EDA_RECT selectionRect( (wxPoint)area.GetOrigin(), wxSize( width, height ) );
424
425 selectionRect.Normalize();
426
427 for( DS_DATA_ITEM* dataItem : DS_DATA_MODEL::GetTheInstance().GetItems() )
428 {
429 for( DS_DRAW_ITEM_BASE* item : dataItem->GetDrawItems() )
430 {
431 if( item->HitTest( selectionRect, windowSelection ) )
432 {
433 if( m_subtractive || ( m_exclusive_or && item->IsSelected() ) )
434 {
435 unselect( item );
436 anySubtracted = true;
437 }
438 else
439 {
440 select( item );
441 anyAdded = true;
442 }
443 }
444 }
445 }
446
447 // Inform other potentially interested tools
448 if( anyAdded )
449 m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
450
451 if( anySubtracted )
452 m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent );
453
454 break; // Stop waiting for events
455 }
456 }
457
458 getViewControls()->SetAutoPan( false );
459
460 // Stop drawing the selection box
461 view->Remove( &area );
462 m_multiple = false; // Multiple selection mode is inactive
463
464 if( !cancelled )
465 m_selection.ClearReferencePoint();
466
467 return cancelled;
468 }
469
470
AddItemToSel(const TOOL_EVENT & aEvent)471 int PL_SELECTION_TOOL::AddItemToSel( const TOOL_EVENT& aEvent )
472 {
473 AddItemToSel( aEvent.Parameter<EDA_ITEM*>() );
474 return 0;
475 }
476
477
AddItemToSel(EDA_ITEM * aItem,bool aQuietMode)478 void PL_SELECTION_TOOL::AddItemToSel( EDA_ITEM* aItem, bool aQuietMode )
479 {
480 if( aItem )
481 {
482 select( aItem );
483
484 // Inform other potentially interested tools
485 if( !aQuietMode )
486 m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
487 }
488 }
489
490
AddItemsToSel(const TOOL_EVENT & aEvent)491 int PL_SELECTION_TOOL::AddItemsToSel( const TOOL_EVENT& aEvent )
492 {
493 AddItemsToSel( aEvent.Parameter<EDA_ITEMS*>(), false );
494 return 0;
495 }
496
497
AddItemsToSel(EDA_ITEMS * aList,bool aQuietMode)498 void PL_SELECTION_TOOL::AddItemsToSel( EDA_ITEMS* aList, bool aQuietMode )
499 {
500 if( aList )
501 {
502 for( EDA_ITEM* item : *aList )
503 select( item );
504
505 // Inform other potentially interested tools
506 if( !aQuietMode )
507 m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
508 }
509 }
510
511
RemoveItemFromSel(const TOOL_EVENT & aEvent)512 int PL_SELECTION_TOOL::RemoveItemFromSel( const TOOL_EVENT& aEvent )
513 {
514 RemoveItemFromSel( aEvent.Parameter<EDA_ITEM*>() );
515 return 0;
516 }
517
518
RemoveItemFromSel(EDA_ITEM * aItem,bool aQuietMode)519 void PL_SELECTION_TOOL::RemoveItemFromSel( EDA_ITEM* aItem, bool aQuietMode )
520 {
521 if( aItem )
522 {
523 unselect( aItem );
524
525 // Inform other potentially interested tools
526 if( !aQuietMode )
527 m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent );
528 }
529 }
530
531
RemoveItemsFromSel(const TOOL_EVENT & aEvent)532 int PL_SELECTION_TOOL::RemoveItemsFromSel( const TOOL_EVENT& aEvent )
533 {
534 RemoveItemsFromSel( aEvent.Parameter<EDA_ITEMS*>(), false );
535 return 0;
536 }
537
538
RemoveItemsFromSel(EDA_ITEMS * aList,bool aQuietMode)539 void PL_SELECTION_TOOL::RemoveItemsFromSel( EDA_ITEMS* aList, bool aQuietMode )
540 {
541 if( aList )
542 {
543 for( EDA_ITEM* item : *aList )
544 unselect( item );
545
546 // Inform other potentially interested tools
547 if( !aQuietMode )
548 m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent );
549 }
550 }
551
552
BrightenItem(EDA_ITEM * aItem)553 void PL_SELECTION_TOOL::BrightenItem( EDA_ITEM* aItem )
554 {
555 highlight( aItem, BRIGHTENED );
556 }
557
558
UnbrightenItem(EDA_ITEM * aItem)559 void PL_SELECTION_TOOL::UnbrightenItem( EDA_ITEM* aItem )
560 {
561 unhighlight( aItem, BRIGHTENED );
562 }
563
564
ClearSelection(const TOOL_EVENT & aEvent)565 int PL_SELECTION_TOOL::ClearSelection( const TOOL_EVENT& aEvent )
566 {
567 ClearSelection();
568 return 0;
569 }
570
571
RebuildSelection()572 void PL_SELECTION_TOOL::RebuildSelection()
573 {
574 m_selection.Clear();
575
576 for( DS_DATA_ITEM* dataItem : DS_DATA_MODEL::GetTheInstance().GetItems() )
577 {
578 for( DS_DRAW_ITEM_BASE* item : dataItem->GetDrawItems() )
579 {
580 if( item->IsSelected() )
581 select( item );
582 }
583 }
584 }
585
586
SelectionMenu(const TOOL_EVENT & aEvent)587 int PL_SELECTION_TOOL::SelectionMenu( const TOOL_EVENT& aEvent )
588 {
589 COLLECTOR* collector = aEvent.Parameter<COLLECTOR*>();
590
591 if( !doSelectionMenu( collector ) )
592 collector->m_MenuCancelled = true;
593
594 return 0;
595 }
596
597
doSelectionMenu(COLLECTOR * aCollector)598 bool PL_SELECTION_TOOL::doSelectionMenu( COLLECTOR* aCollector )
599 {
600 EDA_ITEM* current = nullptr;
601 ACTION_MENU menu( true );
602
603 // ID limit is `MAX_SELECT_ITEM_IDS+1` because the last item is "select all"
604 // and the first item has ID of 1.
605 int limit = std::min( MAX_SELECT_ITEM_IDS + 1, aCollector->GetCount() );
606
607 for( int i = 0; i < limit; ++i )
608 {
609 wxString text;
610 EDA_ITEM* item = ( *aCollector )[i];
611 text = item->GetSelectMenuText( m_frame->GetUserUnits() );
612
613 wxString menuText = wxString::Format( "&%d. %s\t%d", i + 1, text, i + 1 );
614 menu.Add( menuText, i + 1, item->GetMenuImage() );
615 }
616
617 menu.AppendSeparator();
618 menu.Add( _( "Select &All\tA" ), limit + 1, BITMAPS::INVALID_BITMAP );
619
620 if( aCollector->m_MenuTitle.Length() )
621 {
622 menu.SetTitle( aCollector->m_MenuTitle );
623 menu.SetIcon( BITMAPS::info );
624 menu.DisplayTitle( true );
625 }
626 else
627 {
628 menu.DisplayTitle( false );
629 }
630
631 SetContextMenu( &menu, CMENU_NOW );
632
633 bool selectAll = false;
634
635 while( TOOL_EVENT* evt = Wait() )
636 {
637 if( evt->Action() == TA_CHOICE_MENU_UPDATE )
638 {
639 if( selectAll )
640 {
641 for( int i = 0; i < aCollector->GetCount(); ++i )
642 unhighlight( ( *aCollector )[i], BRIGHTENED );
643 }
644 else if( current )
645 {
646 unhighlight( current, BRIGHTENED );
647 }
648
649 int id = *evt->GetCommandId();
650
651 // User has pointed an item, so show it in a different way
652 if( id > 0 && id <= limit )
653 {
654 current = ( *aCollector )[id - 1];
655 highlight( current, BRIGHTENED );
656 }
657 else
658 {
659 current = nullptr;
660 }
661
662 if( id == limit + 1 )
663 {
664 for( int i = 0; i < aCollector->GetCount(); ++i )
665 highlight( ( *aCollector )[i], BRIGHTENED );
666
667 selectAll = true;
668 }
669 else
670 {
671 selectAll = false;
672 }
673 }
674 else if( evt->Action() == TA_CHOICE_MENU_CHOICE )
675 {
676 if( selectAll )
677 {
678 for( int i = 0; i < aCollector->GetCount(); ++i )
679 unhighlight( ( *aCollector )[i], BRIGHTENED );
680 }
681 else if( current )
682 {
683 unhighlight( current, BRIGHTENED );
684 }
685
686 OPT<int> id = evt->GetCommandId();
687
688 // User has selected an item, so this one will be returned
689 if( id == limit + 1 )
690 {
691 selectAll = true;
692 current = nullptr;
693 }
694 else if( id && ( *id > 0 ) && ( *id <= limit ) )
695 {
696 selectAll = false;
697 current = ( *aCollector )[*id - 1];
698 }
699 else
700 {
701 selectAll = false;
702 current = nullptr;
703 }
704 }
705 else if( evt->Action() == TA_CHOICE_MENU_CLOSED )
706 {
707 break;
708 }
709
710 getView()->UpdateItems();
711 m_frame->GetCanvas()->Refresh();
712 }
713
714 if( selectAll )
715 {
716 return true;
717 }
718 else if( current )
719 {
720 unhighlight( current, BRIGHTENED );
721
722 getView()->UpdateItems();
723 m_frame->GetCanvas()->Refresh();
724
725 aCollector->Empty();
726 aCollector->Append( current );
727 return true;
728 }
729
730 return false;
731 }
732
733
ClearSelection()734 void PL_SELECTION_TOOL::ClearSelection()
735 {
736 if( m_selection.Empty() )
737 return;
738
739 while( m_selection.GetSize() )
740 unhighlight( (EDA_ITEM*) m_selection.Front(), SELECTED, &m_selection );
741
742 getView()->Update( &m_selection );
743
744 m_selection.SetIsHover( false );
745 m_selection.ClearReferencePoint();
746
747 // Inform other potentially interested tools
748 m_toolMgr->ProcessEvent( EVENTS::ClearedEvent );
749 }
750
751
select(EDA_ITEM * aItem)752 void PL_SELECTION_TOOL::select( EDA_ITEM* aItem )
753 {
754 highlight( aItem, SELECTED, &m_selection );
755 }
756
757
unselect(EDA_ITEM * aItem)758 void PL_SELECTION_TOOL::unselect( EDA_ITEM* aItem )
759 {
760 unhighlight( aItem, SELECTED, &m_selection );
761 }
762
763
highlight(EDA_ITEM * aItem,int aMode,PL_SELECTION * aGroup)764 void PL_SELECTION_TOOL::highlight( EDA_ITEM* aItem, int aMode, PL_SELECTION* aGroup )
765 {
766 if( aMode == SELECTED )
767 aItem->SetSelected();
768 else if( aMode == BRIGHTENED )
769 aItem->SetBrightened();
770
771 if( aGroup )
772 aGroup->Add( aItem );
773
774 getView()->Update( aItem );
775 }
776
777
unhighlight(EDA_ITEM * aItem,int aMode,PL_SELECTION * aGroup)778 void PL_SELECTION_TOOL::unhighlight( EDA_ITEM* aItem, int aMode, PL_SELECTION* aGroup )
779 {
780 if( aMode == SELECTED )
781 aItem->ClearSelected();
782 else if( aMode == BRIGHTENED )
783 aItem->ClearBrightened();
784
785 if( aGroup )
786 aGroup->Remove( aItem );
787
788 getView()->Update( aItem );
789 }
790
791
selectionContains(const VECTOR2I & aPoint) const792 bool PL_SELECTION_TOOL::selectionContains( const VECTOR2I& aPoint ) const
793 {
794 const unsigned GRIP_MARGIN = 20;
795 VECTOR2I margin = getView()->ToWorld( VECTOR2I( GRIP_MARGIN, GRIP_MARGIN ), false );
796
797 // Check if the point is located within any of the currently selected items bounding boxes
798 for( auto item : m_selection )
799 {
800 BOX2I itemBox = item->ViewBBox();
801 itemBox.Inflate( margin.x, margin.y ); // Give some margin for gripping an item
802
803 if( itemBox.Contains( aPoint ) )
804 return true;
805 }
806
807 return false;
808 }
809
810
setTransitions()811 void PL_SELECTION_TOOL::setTransitions()
812 {
813 Go( &PL_SELECTION_TOOL::UpdateMenu, ACTIONS::updateMenu.MakeEvent() );
814
815 Go( &PL_SELECTION_TOOL::Main, PL_ACTIONS::selectionActivate.MakeEvent() );
816 Go( &PL_SELECTION_TOOL::ClearSelection, PL_ACTIONS::clearSelection.MakeEvent() );
817
818 Go( &PL_SELECTION_TOOL::AddItemToSel, PL_ACTIONS::addItemToSel.MakeEvent() );
819 Go( &PL_SELECTION_TOOL::AddItemsToSel, PL_ACTIONS::addItemsToSel.MakeEvent() );
820 Go( &PL_SELECTION_TOOL::RemoveItemFromSel, PL_ACTIONS::removeItemFromSel.MakeEvent() );
821 Go( &PL_SELECTION_TOOL::RemoveItemsFromSel, PL_ACTIONS::removeItemsFromSel.MakeEvent() );
822 Go( &PL_SELECTION_TOOL::SelectionMenu, PL_ACTIONS::selectionMenu.MakeEvent() );
823
824 Go( &PL_SELECTION_TOOL::disambiguateCursor, EVENTS::DisambiguatePoint );
825 }
826