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