You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

790 lines
22 KiB

  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 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. #include <bitmaps.h>
  25. #include <view/view.h>
  26. #include <view/view_controls.h>
  27. #include <preview_items/selection_area.h>
  28. #include <tool/tool_event.h>
  29. #include <tool/tool_manager.h>
  30. #include <tool/selection.h>
  31. #include <page_layout/ws_data_item.h>
  32. #include <page_layout/ws_data_model.h>
  33. #include <page_layout/ws_draw_item.h>
  34. #include <collector.h>
  35. #include <math/util.h> // for KiROUND
  36. #include "pl_editor_frame.h"
  37. #include "pl_selection_tool.h"
  38. #include "tools/pl_actions.h"
  39. /**
  40. * The maximum number of items in the clarify selection context menu. The current
  41. * setting of 40 is arbitrary.
  42. */
  43. #define MAX_SELECT_ITEM_IDS 40
  44. SELECTION_CONDITION PL_CONDITIONS::Idle = [] (const SELECTION& aSelection )
  45. {
  46. return ( !aSelection.Front() || aSelection.Front()->GetEditFlags() == 0 );
  47. };
  48. #define HITTEST_THRESHOLD_PIXELS 3
  49. PL_SELECTION_TOOL::PL_SELECTION_TOOL() :
  50. TOOL_INTERACTIVE( "plEditor.InteractiveSelection" ),
  51. m_frame( nullptr ),
  52. m_additive( false ),
  53. m_subtractive( false ),
  54. m_exclusive_or( false ),
  55. m_multiple( false ),
  56. m_skip_heuristics( false )
  57. {
  58. }
  59. bool PL_SELECTION_TOOL::Init()
  60. {
  61. m_frame = getEditFrame<PL_EDITOR_FRAME>();
  62. auto& menu = m_menu.GetMenu();
  63. menu.AddSeparator( 200 );
  64. menu.AddItem( PL_ACTIONS::drawLine, PL_CONDITIONS::Idle, 250 );
  65. menu.AddItem( PL_ACTIONS::drawRectangle, PL_CONDITIONS::Idle, 250 );
  66. menu.AddItem( PL_ACTIONS::placeText, PL_CONDITIONS::Idle, 250 );
  67. menu.AddItem( PL_ACTIONS::placeImage, PL_CONDITIONS::Idle, 250 );
  68. menu.AddItem( PL_ACTIONS::appendImportedWorksheet, PL_CONDITIONS::Idle, 250 );
  69. menu.AddSeparator( 1000 );
  70. m_frame->AddStandardSubMenus( m_menu );
  71. return true;
  72. }
  73. void PL_SELECTION_TOOL::Reset( RESET_REASON aReason )
  74. {
  75. if( aReason == MODEL_RELOAD )
  76. m_frame = getEditFrame<PL_EDITOR_FRAME>();
  77. }
  78. int PL_SELECTION_TOOL::UpdateMenu( const TOOL_EVENT& aEvent )
  79. {
  80. ACTION_MENU* actionMenu = aEvent.Parameter<ACTION_MENU*>();
  81. CONDITIONAL_MENU* conditionalMenu = dynamic_cast<CONDITIONAL_MENU*>( actionMenu );
  82. if( conditionalMenu )
  83. conditionalMenu->Evaluate( m_selection );
  84. if( actionMenu )
  85. actionMenu->UpdateAll();
  86. return 0;
  87. }
  88. int PL_SELECTION_TOOL::Main( const TOOL_EVENT& aEvent )
  89. {
  90. // Main loop: keep receiving events
  91. while( TOOL_EVENT* evt = Wait() )
  92. {
  93. m_additive = m_subtractive = m_exclusive_or = false;
  94. if( evt->Modifier( MD_SHIFT ) && evt->Modifier( MD_CTRL ) )
  95. m_subtractive = true;
  96. else if( evt->Modifier( MD_SHIFT ) )
  97. m_additive = true;
  98. else if( evt->Modifier( MD_CTRL ) )
  99. m_exclusive_or = true;
  100. bool modifier_enabled = m_subtractive || m_additive || m_exclusive_or;
  101. // Is the user requesting that the selection list include all possible
  102. // items without removing less likely selection candidates
  103. m_skip_heuristics = !!evt->Modifier( MD_ALT );
  104. // Single click? Select single object
  105. if( evt->IsClick( BUT_LEFT ) )
  106. {
  107. SelectPoint( evt->Position() );
  108. }
  109. // right click? if there is any object - show the context menu
  110. else if( evt->IsClick( BUT_RIGHT ) )
  111. {
  112. bool selectionCancelled = false;
  113. if( m_selection.Empty() )
  114. {
  115. SelectPoint( evt->Position(), &selectionCancelled );
  116. m_selection.SetIsHover( true );
  117. }
  118. if( !selectionCancelled )
  119. m_menu.ShowContextMenu( m_selection );
  120. }
  121. // double click? Display the properties window
  122. else if( evt->IsDblClick( BUT_LEFT ) )
  123. {
  124. // No double-click actions currently defined
  125. }
  126. // drag with LMB? Select multiple objects (or at least draw a selection box) or drag them
  127. else if( evt->IsDrag( BUT_LEFT ) )
  128. {
  129. if( modifier_enabled || m_selection.Empty() )
  130. {
  131. selectMultiple();
  132. }
  133. else
  134. {
  135. // Check if dragging has started within any of selected items bounding box
  136. if( selectionContains( evt->Position() ) )
  137. {
  138. // Yes -> run the move tool and wait till it finishes
  139. m_toolMgr->RunAction( "plEditor.InteractiveMove.move", true );
  140. }
  141. else
  142. {
  143. // No -> clear the selection list
  144. ClearSelection();
  145. }
  146. }
  147. }
  148. // Middle double click? Do zoom to fit or zoom to objects
  149. else if( evt->IsDblClick( BUT_MIDDLE ) )
  150. {
  151. m_toolMgr->RunAction( ACTIONS::zoomFitScreen, true );
  152. }
  153. else if( evt->IsCancelInteractive() )
  154. {
  155. ClearSelection();
  156. }
  157. else if( evt->Action() == TA_UNDO_REDO_PRE )
  158. {
  159. ClearSelection();
  160. }
  161. else
  162. evt->SetPassEvent();
  163. if( m_frame->ToolStackIsEmpty() )
  164. {
  165. if( !modifier_enabled && !m_selection.Empty() && !m_frame->GetDragSelects()
  166. && evt->HasPosition() && selectionContains( evt->Position() ) )
  167. m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::MOVING );
  168. else
  169. {
  170. if( m_additive )
  171. m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::ADD );
  172. else if( m_subtractive )
  173. m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::SUBTRACT );
  174. else if( m_exclusive_or )
  175. m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::XOR );
  176. else
  177. m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::ARROW );
  178. }
  179. }
  180. }
  181. return 0;
  182. }
  183. PL_SELECTION& PL_SELECTION_TOOL::GetSelection()
  184. {
  185. return m_selection;
  186. }
  187. void PL_SELECTION_TOOL::SelectPoint( const VECTOR2I& aWhere, bool* aSelectionCancelledFlag )
  188. {
  189. int threshold = KiROUND( getView()->ToWorld( HITTEST_THRESHOLD_PIXELS ) );
  190. // locate items.
  191. COLLECTOR collector;
  192. for( WS_DATA_ITEM* dataItem : WS_DATA_MODEL::GetTheInstance().GetItems() )
  193. {
  194. for( WS_DRAW_ITEM_BASE* drawItem : dataItem->GetDrawItems() )
  195. {
  196. if( drawItem->HitTest( (wxPoint) aWhere, threshold ) )
  197. collector.Append( drawItem );
  198. }
  199. }
  200. m_selection.ClearReferencePoint();
  201. // Apply some ugly heuristics to avoid disambiguation menus whenever possible
  202. if( collector.GetCount() > 1 && !m_skip_heuristics )
  203. {
  204. guessSelectionCandidates( collector, aWhere );
  205. }
  206. // If still more than one item we're going to have to ask the user.
  207. if( collector.GetCount() > 1 )
  208. {
  209. collector.m_MenuTitle = _( "Clarify Selection" );
  210. // Must call selectionMenu via RunAction() to avoid event-loop contention
  211. m_toolMgr->RunAction( PL_ACTIONS::selectionMenu, true, &collector );
  212. if( collector.m_MenuCancelled )
  213. {
  214. if( aSelectionCancelledFlag )
  215. *aSelectionCancelledFlag = true;
  216. return;
  217. }
  218. }
  219. if( !m_additive && !m_subtractive && !m_exclusive_or )
  220. ClearSelection();
  221. bool anyAdded = false;
  222. bool anySubtracted = false;
  223. if( collector.GetCount() > 0 )
  224. {
  225. for( int i = 0; i < collector.GetCount(); ++i )
  226. {
  227. if( m_subtractive || ( m_exclusive_or && collector[i]->IsSelected() ) )
  228. {
  229. unselect( collector[i] );
  230. anySubtracted = true;
  231. }
  232. else
  233. {
  234. select( collector[i] );
  235. anyAdded = true;
  236. }
  237. }
  238. }
  239. if( anyAdded )
  240. m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
  241. if( anySubtracted )
  242. m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent );
  243. }
  244. void PL_SELECTION_TOOL::guessSelectionCandidates( COLLECTOR& collector, const VECTOR2I& aPos )
  245. {
  246. // There are certain conditions that can be handled automatically.
  247. // Prefer an exact hit to a sloppy one
  248. for( int i = 0; collector.GetCount() == 2 && i < 2; ++i )
  249. {
  250. EDA_ITEM* item = collector[ i ];
  251. EDA_ITEM* other = collector[ ( i + 1 ) % 2 ];
  252. if( item->HitTest( (wxPoint) aPos, 0 ) && !other->HitTest( (wxPoint) aPos, 0 ) )
  253. collector.Transfer( other );
  254. }
  255. }
  256. PL_SELECTION& PL_SELECTION_TOOL::RequestSelection()
  257. {
  258. // If nothing is selected do a hover selection
  259. if( m_selection.Empty() )
  260. {
  261. VECTOR2D cursorPos = getViewControls()->GetCursorPosition( true );
  262. ClearSelection();
  263. SelectPoint( cursorPos );
  264. m_selection.SetIsHover( true );
  265. }
  266. return m_selection;
  267. }
  268. bool PL_SELECTION_TOOL::selectMultiple()
  269. {
  270. bool cancelled = false; // Was the tool cancelled while it was running?
  271. m_multiple = true; // Multiple selection mode is active
  272. KIGFX::VIEW* view = getView();
  273. KIGFX::PREVIEW::SELECTION_AREA area;
  274. view->Add( &area );
  275. while( TOOL_EVENT* evt = Wait() )
  276. {
  277. int width = area.GetEnd().x - area.GetOrigin().x;
  278. /* Selection mode depends on direction of drag-selection:
  279. * Left > Right : Select objects that are fully enclosed by selection
  280. * Right > Left : Select objects that are crossed by selection
  281. */
  282. bool windowSelection = width >= 0 ? true : false;
  283. m_frame->GetCanvas()->SetCurrentCursor(
  284. windowSelection ? KICURSOR::SELECT_WINDOW : KICURSOR::SELECT_LASSO );
  285. if( evt->IsCancelInteractive() || evt->IsActivate() )
  286. {
  287. cancelled = true;
  288. break;
  289. }
  290. if( evt->IsDrag( BUT_LEFT ) )
  291. {
  292. if( !m_additive && !m_subtractive && !m_exclusive_or )
  293. ClearSelection();
  294. // Start drawing a selection box
  295. area.SetOrigin( evt->DragOrigin() );
  296. area.SetEnd( evt->Position() );
  297. area.SetAdditive( m_additive );
  298. area.SetSubtractive( m_subtractive );
  299. area.SetExclusiveOr( m_exclusive_or );
  300. view->SetVisible( &area, true );
  301. view->Update( &area );
  302. getViewControls()->SetAutoPan( true );
  303. }
  304. if( evt->IsMouseUp( BUT_LEFT ) )
  305. {
  306. getViewControls()->SetAutoPan( false );
  307. // End drawing the selection box
  308. view->SetVisible( &area, false );
  309. int height = area.GetEnd().y - area.GetOrigin().y;
  310. bool anyAdded = false;
  311. bool anySubtracted = false;
  312. // Construct an EDA_RECT to determine EDA_ITEM selection
  313. EDA_RECT selectionRect( (wxPoint)area.GetOrigin(), wxSize( width, height ) );
  314. selectionRect.Normalize();
  315. for( WS_DATA_ITEM* dataItem : WS_DATA_MODEL::GetTheInstance().GetItems() )
  316. {
  317. for( WS_DRAW_ITEM_BASE* item : dataItem->GetDrawItems() )
  318. {
  319. if( item->HitTest( selectionRect, windowSelection ) )
  320. {
  321. if( m_subtractive || ( m_exclusive_or && item->IsSelected() ) )
  322. {
  323. unselect( item );
  324. anySubtracted = true;
  325. }
  326. else
  327. {
  328. select( item );
  329. anyAdded = true;
  330. }
  331. }
  332. }
  333. }
  334. // Inform other potentially interested tools
  335. if( anyAdded )
  336. m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
  337. if( anySubtracted )
  338. m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent );
  339. break; // Stop waiting for events
  340. }
  341. }
  342. getViewControls()->SetAutoPan( false );
  343. // Stop drawing the selection box
  344. view->Remove( &area );
  345. m_multiple = false; // Multiple selection mode is inactive
  346. if( !cancelled )
  347. m_selection.ClearReferencePoint();
  348. return cancelled;
  349. }
  350. int PL_SELECTION_TOOL::AddItemToSel( const TOOL_EVENT& aEvent )
  351. {
  352. AddItemToSel( aEvent.Parameter<EDA_ITEM*>() );
  353. return 0;
  354. }
  355. void PL_SELECTION_TOOL::AddItemToSel( EDA_ITEM* aItem, bool aQuietMode )
  356. {
  357. if( aItem )
  358. {
  359. select( aItem );
  360. // Inform other potentially interested tools
  361. if( !aQuietMode )
  362. m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
  363. }
  364. }
  365. int PL_SELECTION_TOOL::AddItemsToSel( const TOOL_EVENT& aEvent )
  366. {
  367. AddItemsToSel( aEvent.Parameter<EDA_ITEMS*>(), false );
  368. return 0;
  369. }
  370. void PL_SELECTION_TOOL::AddItemsToSel( EDA_ITEMS* aList, bool aQuietMode )
  371. {
  372. if( aList )
  373. {
  374. for( EDA_ITEM* item : *aList )
  375. select( item );
  376. // Inform other potentially interested tools
  377. if( !aQuietMode )
  378. m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
  379. }
  380. }
  381. int PL_SELECTION_TOOL::RemoveItemFromSel( const TOOL_EVENT& aEvent )
  382. {
  383. RemoveItemFromSel( aEvent.Parameter<EDA_ITEM*>() );
  384. return 0;
  385. }
  386. void PL_SELECTION_TOOL::RemoveItemFromSel( EDA_ITEM* aItem, bool aQuietMode )
  387. {
  388. if( aItem )
  389. {
  390. unselect( aItem );
  391. // Inform other potentially interested tools
  392. if( !aQuietMode )
  393. m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent );
  394. }
  395. }
  396. int PL_SELECTION_TOOL::RemoveItemsFromSel( const TOOL_EVENT& aEvent )
  397. {
  398. RemoveItemsFromSel( aEvent.Parameter<EDA_ITEMS*>(), false );
  399. return 0;
  400. }
  401. void PL_SELECTION_TOOL::RemoveItemsFromSel( EDA_ITEMS* aList, bool aQuietMode )
  402. {
  403. if( aList )
  404. {
  405. for( EDA_ITEM* item : *aList )
  406. unselect( item );
  407. // Inform other potentially interested tools
  408. if( !aQuietMode )
  409. m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent );
  410. }
  411. }
  412. void PL_SELECTION_TOOL::BrightenItem( EDA_ITEM* aItem )
  413. {
  414. highlight( aItem, BRIGHTENED );
  415. }
  416. void PL_SELECTION_TOOL::UnbrightenItem( EDA_ITEM* aItem )
  417. {
  418. unhighlight( aItem, BRIGHTENED );
  419. }
  420. int PL_SELECTION_TOOL::ClearSelection( const TOOL_EVENT& aEvent )
  421. {
  422. ClearSelection();
  423. return 0;
  424. }
  425. void PL_SELECTION_TOOL::RebuildSelection()
  426. {
  427. m_selection.Clear();
  428. for( WS_DATA_ITEM* dataItem : WS_DATA_MODEL::GetTheInstance().GetItems() )
  429. {
  430. for( WS_DRAW_ITEM_BASE* item : dataItem->GetDrawItems() )
  431. {
  432. if( item->IsSelected() )
  433. select( item );
  434. }
  435. }
  436. }
  437. int PL_SELECTION_TOOL::SelectionMenu( const TOOL_EVENT& aEvent )
  438. {
  439. COLLECTOR* collector = aEvent.Parameter<COLLECTOR*>();
  440. if( !doSelectionMenu( collector ) )
  441. collector->m_MenuCancelled = true;
  442. return 0;
  443. }
  444. bool PL_SELECTION_TOOL::doSelectionMenu( COLLECTOR* aCollector )
  445. {
  446. EDA_ITEM* current = nullptr;
  447. ACTION_MENU menu( true );
  448. // ID limit is `MAX_SELECT_ITEM_IDS+1` because the last item is "select all"
  449. // and the first item has ID of 1.
  450. int limit = std::min( MAX_SELECT_ITEM_IDS + 1, aCollector->GetCount() );
  451. for( int i = 0; i < limit; ++i )
  452. {
  453. wxString text;
  454. EDA_ITEM* item = ( *aCollector )[i];
  455. text = item->GetSelectMenuText( m_frame->GetUserUnits() );
  456. wxString menuText = wxString::Format( "&%d. %s\t%d", i + 1, text, i + 1 );
  457. menu.Add( menuText, i + 1, item->GetMenuImage() );
  458. }
  459. menu.AppendSeparator();
  460. menu.Add( _( "Select &All\tA" ), limit + 1, plus_xpm );
  461. if( aCollector->m_MenuTitle.Length() )
  462. menu.SetTitle( aCollector->m_MenuTitle );
  463. menu.SetIcon( info_xpm );
  464. menu.DisplayTitle( true );
  465. SetContextMenu( &menu, CMENU_NOW );
  466. bool selectAll = false;
  467. while( TOOL_EVENT* evt = Wait() )
  468. {
  469. if( evt->Action() == TA_CHOICE_MENU_UPDATE )
  470. {
  471. if( selectAll )
  472. {
  473. for( int i = 0; i < aCollector->GetCount(); ++i )
  474. unhighlight( ( *aCollector )[i], BRIGHTENED );
  475. }
  476. else if( current )
  477. {
  478. unhighlight( current, BRIGHTENED );
  479. }
  480. int id = *evt->GetCommandId();
  481. // User has pointed an item, so show it in a different way
  482. if( id > 0 && id <= limit )
  483. {
  484. current = ( *aCollector )[id - 1];
  485. highlight( current, BRIGHTENED );
  486. }
  487. else
  488. {
  489. current = nullptr;
  490. }
  491. if( id == limit + 1 )
  492. {
  493. for( int i = 0; i < aCollector->GetCount(); ++i )
  494. highlight( ( *aCollector )[i], BRIGHTENED );
  495. selectAll = true;
  496. }
  497. else
  498. {
  499. selectAll = false;
  500. }
  501. }
  502. else if( evt->Action() == TA_CHOICE_MENU_CHOICE )
  503. {
  504. if( selectAll )
  505. {
  506. for( int i = 0; i < aCollector->GetCount(); ++i )
  507. unhighlight( ( *aCollector )[i], BRIGHTENED );
  508. }
  509. else if( current )
  510. {
  511. unhighlight( current, BRIGHTENED );
  512. }
  513. OPT<int> id = evt->GetCommandId();
  514. // User has selected an item, so this one will be returned
  515. if( id == limit + 1 )
  516. {
  517. selectAll = true;
  518. current = nullptr;
  519. }
  520. else if( id && ( *id > 0 ) && ( *id <= limit ) )
  521. {
  522. selectAll = false;
  523. current = ( *aCollector )[*id - 1];
  524. }
  525. else
  526. {
  527. selectAll = false;
  528. current = nullptr;
  529. }
  530. }
  531. else if( evt->Action() == TA_CHOICE_MENU_CLOSED )
  532. {
  533. break;
  534. }
  535. getView()->UpdateItems();
  536. m_frame->GetCanvas()->Refresh();
  537. }
  538. if( selectAll )
  539. {
  540. return true;
  541. }
  542. else if( current )
  543. {
  544. unhighlight( current, BRIGHTENED );
  545. getView()->UpdateItems();
  546. m_frame->GetCanvas()->Refresh();
  547. aCollector->Empty();
  548. aCollector->Append( current );
  549. return true;
  550. }
  551. return false;
  552. }
  553. void PL_SELECTION_TOOL::ClearSelection()
  554. {
  555. if( m_selection.Empty() )
  556. return;
  557. while( m_selection.GetSize() )
  558. unhighlight( (EDA_ITEM*) m_selection.Front(), SELECTED, &m_selection );
  559. getView()->Update( &m_selection );
  560. m_selection.SetIsHover( false );
  561. m_selection.ClearReferencePoint();
  562. // Inform other potentially interested tools
  563. m_toolMgr->ProcessEvent( EVENTS::ClearedEvent );
  564. }
  565. void PL_SELECTION_TOOL::select( EDA_ITEM* aItem )
  566. {
  567. highlight( aItem, SELECTED, &m_selection );
  568. }
  569. void PL_SELECTION_TOOL::unselect( EDA_ITEM* aItem )
  570. {
  571. unhighlight( aItem, SELECTED, &m_selection );
  572. }
  573. void PL_SELECTION_TOOL::highlight( EDA_ITEM* aItem, int aMode, PL_SELECTION* aGroup )
  574. {
  575. if( aMode == SELECTED )
  576. aItem->SetSelected();
  577. else if( aMode == BRIGHTENED )
  578. aItem->SetBrightened();
  579. if( aGroup )
  580. aGroup->Add( aItem );
  581. getView()->Update( aItem );
  582. }
  583. void PL_SELECTION_TOOL::unhighlight( EDA_ITEM* aItem, int aMode, PL_SELECTION* aGroup )
  584. {
  585. if( aMode == SELECTED )
  586. aItem->ClearSelected();
  587. else if( aMode == BRIGHTENED )
  588. aItem->ClearBrightened();
  589. if( aGroup )
  590. aGroup->Remove( aItem );
  591. getView()->Update( aItem );
  592. }
  593. bool PL_SELECTION_TOOL::selectionContains( const VECTOR2I& aPoint ) const
  594. {
  595. const unsigned GRIP_MARGIN = 20;
  596. VECTOR2I margin = getView()->ToWorld( VECTOR2I( GRIP_MARGIN, GRIP_MARGIN ), false );
  597. // Check if the point is located within any of the currently selected items bounding boxes
  598. for( auto item : m_selection )
  599. {
  600. BOX2I itemBox = item->ViewBBox();
  601. itemBox.Inflate( margin.x, margin.y ); // Give some margin for gripping an item
  602. if( itemBox.Contains( aPoint ) )
  603. return true;
  604. }
  605. return false;
  606. }
  607. void PL_SELECTION_TOOL::setTransitions()
  608. {
  609. Go( &PL_SELECTION_TOOL::UpdateMenu, ACTIONS::updateMenu.MakeEvent() );
  610. Go( &PL_SELECTION_TOOL::Main, PL_ACTIONS::selectionActivate.MakeEvent() );
  611. Go( &PL_SELECTION_TOOL::ClearSelection, PL_ACTIONS::clearSelection.MakeEvent() );
  612. Go( &PL_SELECTION_TOOL::AddItemToSel, PL_ACTIONS::addItemToSel.MakeEvent() );
  613. Go( &PL_SELECTION_TOOL::AddItemsToSel, PL_ACTIONS::addItemsToSel.MakeEvent() );
  614. Go( &PL_SELECTION_TOOL::RemoveItemFromSel, PL_ACTIONS::removeItemFromSel.MakeEvent() );
  615. Go( &PL_SELECTION_TOOL::RemoveItemsFromSel, PL_ACTIONS::removeItemsFromSel.MakeEvent() );
  616. Go( &PL_SELECTION_TOOL::SelectionMenu, PL_ACTIONS::selectionMenu.MakeEvent() );
  617. }