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.

1216 lines
34 KiB

13 years ago
13 years ago
3 years ago
5 years ago
3 years ago
5 years ago
5 years ago
11 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
5 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
5 years ago
3 years ago
  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2013-2018 CERN
  5. * Copyright (C) 2019-2023 KiCad Developers, see AUTHORS.txt for contributors.
  6. * @author Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
  7. * @author Maciej Suminski <maciej.suminski@cern.ch>
  8. *
  9. * This program is free software; you can redistribute it and/or
  10. * modify it under the terms of the GNU General Public License
  11. * as published by the Free Software Foundation; either version 2
  12. * of the License, or (at your option) any later version.
  13. *
  14. * This program is distributed in the hope that it will be useful,
  15. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. * GNU General Public License for more details.
  18. *
  19. * You should have received a copy of the GNU General Public License
  20. * along with this program; if not, you may find one here:
  21. * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  22. * or you may search the http://www.gnu.org website for the version 2 license,
  23. * or you may write to the Free Software Foundation, Inc.,
  24. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  25. */
  26. #include <core/kicad_algo.h>
  27. #include <optional>
  28. #include <map>
  29. #include <stack>
  30. #include <trace_helpers.h>
  31. #include <wx/event.h>
  32. #include <wx/clipbrd.h>
  33. #include <wx/app.h>
  34. #include <math/vector2wx.h>
  35. #include <view/view.h>
  36. #include <eda_base_frame.h>
  37. #include <tool/tool_base.h>
  38. #include <tool/tool_interactive.h>
  39. #include <tool/tool_manager.h>
  40. #include <tool/action_menu.h>
  41. #include <tool/coroutine.h>
  42. #include <tool/action_manager.h>
  43. #include <class_draw_panel_gal.h>
  44. /// Struct describing the current execution state of a TOOL
  45. struct TOOL_MANAGER::TOOL_STATE
  46. {
  47. TOOL_STATE( TOOL_BASE* aTool ) :
  48. theTool( aTool )
  49. {
  50. clear();
  51. }
  52. TOOL_STATE( const TOOL_STATE& aState )
  53. {
  54. theTool = aState.theTool;
  55. idle = aState.idle;
  56. shutdown = aState.shutdown;
  57. pendingWait = aState.pendingWait;
  58. pendingContextMenu = aState.pendingContextMenu;
  59. contextMenu = aState.contextMenu;
  60. contextMenuTrigger = aState.contextMenuTrigger;
  61. cofunc = aState.cofunc;
  62. initialEvent = aState.initialEvent;
  63. wakeupEvent = aState.wakeupEvent;
  64. waitEvents = aState.waitEvents;
  65. transitions = aState.transitions;
  66. vcSettings = aState.vcSettings;
  67. // do not copy stateStack
  68. }
  69. ~TOOL_STATE()
  70. {
  71. if( !stateStack.empty() )
  72. wxFAIL;
  73. }
  74. /// The tool itself
  75. TOOL_BASE* theTool;
  76. /// Is the tool active (pending execution) or disabled at the moment
  77. bool idle;
  78. /// Should the tool shutdown during next execution
  79. bool shutdown;
  80. /// Flag defining if the tool is waiting for any event (i.e. if it
  81. /// issued a Wait() call).
  82. bool pendingWait;
  83. /// Is there a context menu being displayed
  84. bool pendingContextMenu;
  85. /// Context menu currently used by the tool
  86. ACTION_MENU* contextMenu;
  87. /// Defines when the context menu is opened
  88. CONTEXT_MENU_TRIGGER contextMenuTrigger;
  89. /// Tool execution context
  90. COROUTINE<int, const TOOL_EVENT&>* cofunc;
  91. /// The first event that triggered activation of the tool.
  92. TOOL_EVENT initialEvent;
  93. /// The event that triggered the execution/wakeup of the tool after Wait() call
  94. TOOL_EVENT wakeupEvent;
  95. /// List of events the tool is currently waiting for
  96. TOOL_EVENT_LIST waitEvents;
  97. /// List of possible transitions (ie. association of events and state handlers that are executed
  98. /// upon the event reception
  99. std::vector<TRANSITION> transitions;
  100. /// VIEW_CONTROLS settings to preserve settings when the tools are switched
  101. KIGFX::VC_SETTINGS vcSettings;
  102. TOOL_STATE& operator=( const TOOL_STATE& aState )
  103. {
  104. theTool = aState.theTool;
  105. idle = aState.idle;
  106. shutdown = aState.shutdown;
  107. pendingWait = aState.pendingWait;
  108. pendingContextMenu = aState.pendingContextMenu;
  109. contextMenu = aState.contextMenu;
  110. contextMenuTrigger = aState.contextMenuTrigger;
  111. cofunc = aState.cofunc;
  112. initialEvent = aState.initialEvent;
  113. wakeupEvent = aState.wakeupEvent;
  114. waitEvents = aState.waitEvents;
  115. transitions = aState.transitions;
  116. vcSettings = aState.vcSettings;
  117. // do not copy stateStack
  118. return *this;
  119. }
  120. bool operator==( const TOOL_MANAGER::TOOL_STATE& aRhs ) const
  121. {
  122. return aRhs.theTool == theTool;
  123. }
  124. bool operator!=( const TOOL_MANAGER::TOOL_STATE& aRhs ) const
  125. {
  126. return aRhs.theTool != theTool;
  127. }
  128. /**
  129. * Store the current state of the tool on stack. Stacks are stored internally and are not
  130. * shared between different TOOL_STATE objects.
  131. */
  132. void Push()
  133. {
  134. auto state = std::make_unique<TOOL_STATE>( *this );
  135. stateStack.push( std::move( state ) );
  136. clear();
  137. }
  138. /**
  139. * Restore state of the tool from stack. Stacks are stored internally and are not
  140. * shared between different TOOL_STATE objects.
  141. *
  142. * @return True if state was restored, false if the stack was empty.
  143. */
  144. bool Pop()
  145. {
  146. delete cofunc;
  147. if( !stateStack.empty() )
  148. {
  149. *this = *stateStack.top().get();
  150. stateStack.pop();
  151. return true;
  152. }
  153. else
  154. {
  155. cofunc = nullptr;
  156. return false;
  157. }
  158. }
  159. private:
  160. ///< Stack preserving previous states of a TOOL.
  161. std::stack<std::unique_ptr<TOOL_STATE>> stateStack;
  162. ///< Restores the initial state.
  163. void clear()
  164. {
  165. idle = true;
  166. shutdown = false;
  167. pendingWait = false;
  168. pendingContextMenu = false;
  169. cofunc = nullptr;
  170. contextMenu = nullptr;
  171. contextMenuTrigger = CMENU_OFF;
  172. vcSettings.Reset();
  173. transitions.clear();
  174. }
  175. };
  176. TOOL_MANAGER::TOOL_MANAGER() :
  177. m_model( nullptr ),
  178. m_view( nullptr ),
  179. m_viewControls( nullptr ),
  180. m_frame( nullptr ),
  181. m_settings( nullptr ),
  182. m_warpMouseAfterContextMenu( true ),
  183. m_menuActive( false ),
  184. m_menuOwner( -1 ),
  185. m_activeState( nullptr ),
  186. m_shuttingDown( false )
  187. {
  188. m_actionMgr = new ACTION_MANAGER( this );
  189. }
  190. TOOL_MANAGER::~TOOL_MANAGER()
  191. {
  192. std::map<TOOL_BASE*, TOOL_STATE*>::iterator it, it_end;
  193. for( it = m_toolState.begin(), it_end = m_toolState.end(); it != it_end; ++it )
  194. {
  195. delete it->second->cofunc; // delete cofunction
  196. delete it->second; // delete TOOL_STATE
  197. delete it->first; // delete the tool itself
  198. }
  199. delete m_actionMgr;
  200. }
  201. void TOOL_MANAGER::RegisterTool( TOOL_BASE* aTool )
  202. {
  203. wxASSERT_MSG( m_toolNameIndex.find( aTool->GetName() ) == m_toolNameIndex.end(),
  204. wxT( "Adding two tools with the same name may result in unexpected behavior.") );
  205. wxASSERT_MSG( m_toolIdIndex.find( aTool->GetId() ) == m_toolIdIndex.end(),
  206. wxT( "Adding two tools with the same ID may result in unexpected behavior.") );
  207. wxASSERT_MSG( m_toolTypes.find( typeid( *aTool ).name() ) == m_toolTypes.end(),
  208. wxT( "Adding two tools of the same type may result in unexpected behavior.") );
  209. m_toolOrder.push_back( aTool );
  210. TOOL_STATE* st = new TOOL_STATE( aTool );
  211. m_toolState[aTool] = st;
  212. m_toolNameIndex[aTool->GetName()] = st;
  213. m_toolIdIndex[aTool->GetId()] = st;
  214. m_toolTypes[typeid( *aTool ).name()] = st->theTool;
  215. aTool->attachManager( this );
  216. }
  217. bool TOOL_MANAGER::InvokeTool( TOOL_ID aToolId )
  218. {
  219. TOOL_BASE* tool = FindTool( aToolId );
  220. if( tool && tool->GetType() == INTERACTIVE )
  221. return invokeTool( tool );
  222. wxLogTrace( kicadTraceToolStack, wxS( "TOOL_MANAGER::InvokeTool - no tool with ID %d" ),
  223. aToolId );
  224. return false; // there is no tool with the given id
  225. }
  226. bool TOOL_MANAGER::InvokeTool( const std::string& aToolName )
  227. {
  228. TOOL_BASE* tool = FindTool( aToolName );
  229. if( tool && tool->GetType() == INTERACTIVE )
  230. return invokeTool( tool );
  231. wxLogTrace( kicadTraceToolStack, wxS( "TOOL_MANAGER::InvokeTool - no tool with name %s" ),
  232. aToolName );
  233. return false; // there is no tool with the given name
  234. }
  235. bool TOOL_MANAGER::RunAction( const std::string& aActionName, bool aNow, void* aParam )
  236. {
  237. TOOL_ACTION* action = m_actionMgr->FindAction( aActionName );
  238. if( !action )
  239. {
  240. wxASSERT_MSG( false, wxString::Format( "Could not find action %s.", aActionName ) );
  241. return false;
  242. }
  243. RunAction( *action, aNow, aParam );
  244. return false;
  245. }
  246. VECTOR2D TOOL_MANAGER::GetMousePosition() const
  247. {
  248. if( m_viewControls )
  249. return m_viewControls->GetMousePosition();
  250. else
  251. return ToVECTOR2D( wxGetMousePosition() );
  252. }
  253. VECTOR2D TOOL_MANAGER::GetCursorPosition() const
  254. {
  255. if( m_viewControls )
  256. return m_viewControls->GetCursorPosition();
  257. else
  258. return ToVECTOR2D( wxGetMousePosition() );
  259. }
  260. bool TOOL_MANAGER::RunAction( const TOOL_ACTION& aAction, bool aNow, void* aParam )
  261. {
  262. if( m_shuttingDown )
  263. return true;
  264. bool handled = false;
  265. TOOL_EVENT event = aAction.MakeEvent();
  266. if( event.Category() == TC_COMMAND )
  267. event.SetMousePosition( GetCursorPosition() );
  268. // Allow to override the action parameter
  269. if( aParam )
  270. event.SetParameter( aParam );
  271. if( aNow )
  272. {
  273. TOOL_STATE* current = m_activeState;
  274. handled = processEvent( event );
  275. setActiveState( current );
  276. UpdateUI( event );
  277. }
  278. else
  279. {
  280. PostEvent( event );
  281. }
  282. return handled;
  283. }
  284. void TOOL_MANAGER::CancelTool()
  285. {
  286. TOOL_EVENT evt( TC_COMMAND, TA_CANCEL_TOOL );
  287. processEvent( evt );
  288. }
  289. void TOOL_MANAGER::PrimeTool( const VECTOR2D& aPosition )
  290. {
  291. int modifiers = 0;
  292. /*
  293. * Don't include any modifiers. They're part of the hotkey, not part of the resulting
  294. * click.
  295. *
  296. * modifiers |= wxGetKeyState( WXK_SHIFT ) ? MD_SHIFT : 0;
  297. * modifiers |= wxGetKeyState( WXK_CONTROL ) ? MD_CTRL : 0;
  298. * modifiers |= wxGetKeyState( WXK_ALT ) ? MD_ALT : 0;
  299. */
  300. TOOL_EVENT evt( TC_MOUSE, TA_PRIME, BUT_LEFT | modifiers );
  301. evt.SetMousePosition( aPosition );
  302. PostEvent( evt );
  303. }
  304. void TOOL_MANAGER::PostEvent( const TOOL_EVENT& aEvent )
  305. {
  306. // Horrific hack, but it's a crash bug. Don't let inter-frame commands stack up
  307. // waiting to be processed.
  308. if( aEvent.IsSimulator() && m_eventQueue.size() > 0 && m_eventQueue.back().IsSimulator() )
  309. m_eventQueue.pop_back();
  310. m_eventQueue.push_back( aEvent );
  311. }
  312. int TOOL_MANAGER::GetHotKey( const TOOL_ACTION& aAction ) const
  313. {
  314. return m_actionMgr->GetHotKey( aAction );
  315. }
  316. bool TOOL_MANAGER::invokeTool( TOOL_BASE* aTool )
  317. {
  318. wxASSERT( aTool != nullptr );
  319. TOOL_EVENT evt( TC_COMMAND, TA_ACTIVATE, aTool->GetName() );
  320. evt.SetMousePosition( GetCursorPosition() );
  321. processEvent( evt );
  322. if( TOOL_STATE* active = GetCurrentToolState() )
  323. setActiveState( active );
  324. return true;
  325. }
  326. bool TOOL_MANAGER::runTool( TOOL_BASE* aTool )
  327. {
  328. wxASSERT( aTool != nullptr );
  329. if( !isRegistered( aTool ) )
  330. {
  331. wxASSERT_MSG( false, wxT( "You cannot run unregistered tools" ) );
  332. return false;
  333. }
  334. TOOL_ID id = aTool->GetId();
  335. wxLogTrace( kicadTraceToolStack, wxS( "TOOL_MANAGER::runTool - running tool %s" ),
  336. aTool->GetName() );
  337. if( aTool->GetType() == INTERACTIVE )
  338. static_cast<TOOL_INTERACTIVE*>( aTool )->resetTransitions();
  339. // If the tool is already active, bring it to the top of the active tools stack
  340. if( isActive( aTool ) && m_activeTools.size() > 1 )
  341. {
  342. auto it = std::find( m_activeTools.begin(), m_activeTools.end(), id );
  343. if( it != m_activeTools.end() )
  344. {
  345. if( it != m_activeTools.begin() )
  346. {
  347. m_activeTools.erase( it );
  348. m_activeTools.push_front( id );
  349. }
  350. return false;
  351. }
  352. }
  353. setActiveState( m_toolIdIndex[id] );
  354. aTool->Reset( TOOL_INTERACTIVE::RUN );
  355. // Add the tool on the front of the processing queue (it gets events first)
  356. m_activeTools.push_front( id );
  357. return true;
  358. }
  359. void TOOL_MANAGER::ShutdownAllTools()
  360. {
  361. m_shuttingDown = true;
  362. // Create a temporary list of tools to iterate over since when the tools shutdown
  363. // they remove themselves from the list automatically (invalidating the iterator)
  364. ID_LIST tmpList = m_activeTools;
  365. // Make sure each tool knows that it is shutting down, so that loops get shut down
  366. // at the dispatcher
  367. for( auto id : tmpList )
  368. {
  369. if( m_toolIdIndex.count( id ) == 0 )
  370. continue;
  371. m_toolIdIndex[id]->shutdown = true;
  372. }
  373. for( auto id : tmpList )
  374. {
  375. ShutdownTool( id );
  376. }
  377. }
  378. void TOOL_MANAGER::ShutdownTool( TOOL_ID aToolId )
  379. {
  380. TOOL_BASE* tool = FindTool( aToolId );
  381. if( tool && tool->GetType() == INTERACTIVE )
  382. ShutdownTool( tool );
  383. wxLogTrace( kicadTraceToolStack, wxS( "TOOL_MANAGER::ShutdownTool - no tool with ID %d" ),
  384. aToolId );
  385. }
  386. void TOOL_MANAGER::ShutdownTool( const std::string& aToolName )
  387. {
  388. TOOL_BASE* tool = FindTool( aToolName );
  389. if( tool && tool->GetType() == INTERACTIVE )
  390. ShutdownTool( tool );
  391. wxLogTrace( kicadTraceToolStack, wxS( "TOOL_MANAGER::ShutdownTool - no tool with name %s" ),
  392. aToolName );
  393. }
  394. void TOOL_MANAGER::ShutdownTool( TOOL_BASE* aTool )
  395. {
  396. wxASSERT( aTool != nullptr );
  397. TOOL_ID id = aTool->GetId();
  398. if( isActive( aTool ) )
  399. {
  400. TOOL_MANAGER::ID_LIST::iterator it = std::find( m_activeTools.begin(),
  401. m_activeTools.end(), id );
  402. TOOL_STATE* st = m_toolIdIndex[*it];
  403. // the tool state handler is waiting for events (i.e. called Wait() method)
  404. if( st && st->pendingWait )
  405. {
  406. // Wake up the tool and tell it to shutdown
  407. st->shutdown = true;
  408. st->pendingWait = false;
  409. st->waitEvents.clear();
  410. if( st->cofunc )
  411. {
  412. wxLogTrace( kicadTraceToolStack,
  413. wxS( "TOOL_MANAGER::ShutdownTool - Shutting down tool %s" ),
  414. st->theTool->GetName() );
  415. setActiveState( st );
  416. bool end = !st->cofunc->Resume();
  417. if( end )
  418. finishTool( st );
  419. }
  420. }
  421. }
  422. }
  423. TOOL_BASE* TOOL_MANAGER::FindTool( int aId ) const
  424. {
  425. std::map<TOOL_ID, TOOL_STATE*>::const_iterator it = m_toolIdIndex.find( aId );
  426. if( it != m_toolIdIndex.end() )
  427. return it->second->theTool;
  428. return nullptr;
  429. }
  430. TOOL_BASE* TOOL_MANAGER::FindTool( const std::string& aName ) const
  431. {
  432. std::map<std::string, TOOL_STATE*>::const_iterator it = m_toolNameIndex.find( aName );
  433. if( it != m_toolNameIndex.end() )
  434. return it->second->theTool;
  435. return nullptr;
  436. }
  437. void TOOL_MANAGER::DeactivateTool()
  438. {
  439. // Deactivate the active tool, but do not run anything new
  440. TOOL_EVENT evt( TC_COMMAND, TA_CANCEL_TOOL );
  441. processEvent( evt );
  442. }
  443. void TOOL_MANAGER::ResetTools( TOOL_BASE::RESET_REASON aReason )
  444. {
  445. if( aReason != TOOL_BASE::REDRAW )
  446. DeactivateTool();
  447. for( auto& state : m_toolState )
  448. {
  449. TOOL_BASE* tool = state.first;
  450. setActiveState( state.second );
  451. tool->Reset( aReason );
  452. if( tool->GetType() == INTERACTIVE )
  453. static_cast<TOOL_INTERACTIVE*>( tool )->resetTransitions();
  454. }
  455. }
  456. void TOOL_MANAGER::InitTools()
  457. {
  458. for( auto it = m_toolOrder.begin(); it != m_toolOrder.end(); /* iter inside */ )
  459. {
  460. TOOL_BASE* tool = *it;
  461. wxASSERT( m_toolState.count( tool ) );
  462. TOOL_STATE* state = m_toolState[tool];
  463. setActiveState( state );
  464. ++it; // keep the iterator valid if the element is going to be erased
  465. if( !tool->Init() )
  466. {
  467. wxLogTrace( kicadTraceToolStack, wxS( "TOOL_MANAGER initialization of tool '%s' failed" ),
  468. tool->GetName() );
  469. // Unregister the tool
  470. setActiveState( nullptr );
  471. m_toolState.erase( tool );
  472. m_toolNameIndex.erase( tool->GetName() );
  473. m_toolIdIndex.erase( tool->GetId() );
  474. m_toolTypes.erase( typeid( *tool ).name() );
  475. delete state;
  476. delete tool;
  477. }
  478. }
  479. m_actionMgr->UpdateHotKeys( true );
  480. ResetTools( TOOL_BASE::RUN );
  481. }
  482. int TOOL_MANAGER::GetPriority( int aToolId ) const
  483. {
  484. int priority = 0;
  485. for( TOOL_ID tool : m_activeTools )
  486. {
  487. if( tool == aToolId )
  488. return priority;
  489. ++priority;
  490. }
  491. return -1;
  492. }
  493. void TOOL_MANAGER::ScheduleNextState( TOOL_BASE* aTool, TOOL_STATE_FUNC& aHandler,
  494. const TOOL_EVENT_LIST& aConditions )
  495. {
  496. TOOL_STATE* st = m_toolState[aTool];
  497. st->transitions.emplace_back( TRANSITION( aConditions, aHandler ) );
  498. }
  499. void TOOL_MANAGER::ClearTransitions( TOOL_BASE* aTool )
  500. {
  501. m_toolState[aTool]->transitions.clear();
  502. }
  503. void TOOL_MANAGER::RunMainStack( TOOL_BASE* aTool, std::function<void()> aFunc )
  504. {
  505. TOOL_STATE* st = m_toolState[aTool];
  506. setActiveState( st );
  507. st->cofunc->RunMainStack( std::move( aFunc ) );
  508. }
  509. TOOL_EVENT* TOOL_MANAGER::ScheduleWait( TOOL_BASE* aTool, const TOOL_EVENT_LIST& aConditions )
  510. {
  511. TOOL_STATE* st = m_toolState[aTool];
  512. wxASSERT( !st->pendingWait ); // everything collapses on two KiYield() in a row
  513. // indicate to the manager that we are going to sleep and we shall be
  514. // woken up when an event matching aConditions arrive
  515. st->pendingWait = true;
  516. st->waitEvents = aConditions;
  517. // switch context back to event dispatcher loop
  518. st->cofunc->KiYield();
  519. // If the tool should shutdown, it gets a null event to break the loop
  520. if( st->shutdown )
  521. return nullptr;
  522. else
  523. return &st->wakeupEvent;
  524. }
  525. bool TOOL_MANAGER::dispatchInternal( TOOL_EVENT& aEvent )
  526. {
  527. bool handled = false;
  528. wxLogTrace( kicadTraceToolStack, wxS( "TOOL_MANAGER::dispatchInternal - received event: %s" ),
  529. aEvent.Format() );
  530. auto it = m_activeTools.begin();
  531. // iterate over active tool stack
  532. while( it != m_activeTools.end() )
  533. {
  534. TOOL_STATE* st = m_toolIdIndex[*it];
  535. bool increment = true;
  536. // forward context menu events to the tool that created the menu
  537. if( aEvent.IsChoiceMenu() )
  538. {
  539. if( *it != m_menuOwner )
  540. {
  541. ++it;
  542. continue;
  543. }
  544. }
  545. // If we're pendingWait then we had better have a cofunc to process the wait.
  546. wxASSERT( !st || !st->pendingWait || st->cofunc );
  547. // the tool state handler is waiting for events (i.e. called Wait() method)
  548. if( st && st->cofunc && st->pendingWait && st->waitEvents.Matches( aEvent ) )
  549. {
  550. if( !aEvent.FirstResponder() )
  551. aEvent.SetFirstResponder( st->theTool );
  552. // got matching event? clear wait list and wake up the coroutine
  553. st->wakeupEvent = aEvent;
  554. st->pendingWait = false;
  555. st->waitEvents.clear();
  556. wxLogTrace( kicadTraceToolStack,
  557. wxS( "TOOL_MANAGER::dispatchInternal - Waking tool %s for event: %s" ),
  558. st->theTool->GetName(), aEvent.Format() );
  559. setActiveState( st );
  560. bool end = !st->cofunc->Resume();
  561. if( end )
  562. {
  563. it = finishTool( st );
  564. increment = false;
  565. }
  566. // If the tool did not request the event be passed to other tools, we're done
  567. if( !st->wakeupEvent.PassEvent() )
  568. {
  569. wxLogTrace( kicadTraceToolStack,
  570. wxS( "TOOL_MANAGER::dispatchInternal - tool %s stopped passing event: %s" ),
  571. st->theTool->GetName(), aEvent.Format() );
  572. return true;
  573. }
  574. }
  575. if( increment )
  576. ++it;
  577. }
  578. for( const auto& state : m_toolState )
  579. {
  580. TOOL_STATE* st = state.second;
  581. bool finished = false;
  582. // no state handler in progress - check if there are any transitions (defined by
  583. // Go() method that match the event.
  584. if( !st->transitions.empty() )
  585. {
  586. for( const TRANSITION& tr : st->transitions )
  587. {
  588. if( tr.first.Matches( aEvent ) )
  589. {
  590. auto func_copy = tr.second;
  591. if( !aEvent.FirstResponder() )
  592. aEvent.SetFirstResponder( st->theTool );
  593. // if there is already a context, then push it on the stack
  594. // and transfer the previous view control settings to the new context
  595. if( st->cofunc )
  596. {
  597. auto vc = st->vcSettings;
  598. st->Push();
  599. st->vcSettings = vc;
  600. }
  601. st->cofunc = new COROUTINE<int, const TOOL_EVENT&>( std::move( func_copy ) );
  602. // as the state changes, the transition table has to be set up again
  603. st->transitions.clear();
  604. wxLogTrace( kicadTraceToolStack,
  605. wxS( "TOOL_MANAGER::dispatchInternal - Running tool %s for event: %s" ),
  606. st->theTool->GetName(), aEvent.Format() );
  607. // got match? Run the handler.
  608. setActiveState( st );
  609. st->idle = false;
  610. st->initialEvent = aEvent;
  611. st->cofunc->Call( st->initialEvent );
  612. handled = true;
  613. if( !st->cofunc->Running() )
  614. finishTool( st ); // The coroutine has finished immediately?
  615. // if it is a message, continue processing
  616. finished = !( aEvent.Category() == TC_MESSAGE );
  617. // there is no point in further checking, as transitions got cleared
  618. break;
  619. }
  620. }
  621. }
  622. if( finished )
  623. break; // only the first tool gets the event
  624. }
  625. wxLogTrace( kicadTraceToolStack, wxS( "TOOL_MANAGER::dispatchInternal - %s handle event: %s" ),
  626. ( handled ? wxS( "Did" ) : wxS( "Did not" ) ), aEvent.Format() );
  627. return handled;
  628. }
  629. bool TOOL_MANAGER::DispatchHotKey( const TOOL_EVENT& aEvent )
  630. {
  631. if( aEvent.Action() == TA_KEY_PRESSED )
  632. return m_actionMgr->RunHotKey( aEvent.Modifier() | aEvent.KeyCode() );
  633. return false;
  634. }
  635. bool TOOL_MANAGER::dispatchActivation( const TOOL_EVENT& aEvent )
  636. {
  637. wxLogTrace( kicadTraceToolStack, wxS( "TOOL_MANAGER::dispatchActivation - Received event: %s" ),
  638. aEvent.Format() );
  639. if( aEvent.IsActivate() )
  640. {
  641. auto tool = m_toolNameIndex.find( aEvent.getCommandStr() );
  642. if( tool != m_toolNameIndex.end() )
  643. {
  644. wxLogTrace( kicadTraceToolStack,
  645. wxS( "TOOL_MANAGER::dispatchActivation - Running tool %s for event: %s" ),
  646. tool->second->theTool->GetName(), aEvent.Format() );
  647. runTool( tool->second->theTool );
  648. return true;
  649. }
  650. }
  651. return false;
  652. }
  653. void TOOL_MANAGER::DispatchContextMenu( const TOOL_EVENT& aEvent )
  654. {
  655. for( TOOL_ID toolId : m_activeTools )
  656. {
  657. TOOL_STATE* st = m_toolIdIndex[toolId];
  658. // the tool requested a context menu. The menu is activated on RMB click (CMENU_BUTTON mode)
  659. // or immediately (CMENU_NOW) mode. The latter is used for clarification lists.
  660. if( st->contextMenuTrigger == CMENU_OFF )
  661. continue;
  662. if( st->contextMenuTrigger == CMENU_BUTTON && !aEvent.IsClick( BUT_RIGHT ) )
  663. break;
  664. if( st->cofunc )
  665. {
  666. st->pendingWait = true;
  667. st->waitEvents = TOOL_EVENT( TC_ANY, TA_ANY );
  668. }
  669. // Store the menu pointer in case it is changed by the TOOL when handling menu events
  670. ACTION_MENU* m = st->contextMenu;
  671. if( st->contextMenuTrigger == CMENU_NOW )
  672. st->contextMenuTrigger = CMENU_OFF;
  673. // Store the cursor position, so the tools could execute actions
  674. // using the point where the user has invoked a context menu
  675. if( m_viewControls )
  676. m_menuCursor = m_viewControls->GetCursorPosition();
  677. // Save all tools cursor settings, as they will be overridden
  678. for( const std::pair<const TOOL_ID, TOOL_STATE*>& idState : m_toolIdIndex )
  679. {
  680. TOOL_STATE* s = idState.second;
  681. const auto& vc = s->vcSettings;
  682. if( vc.m_forceCursorPosition )
  683. m_cursorSettings[idState.first] = vc.m_forcedPosition;
  684. else
  685. m_cursorSettings[idState.first] = std::nullopt;
  686. }
  687. if( m_viewControls )
  688. m_viewControls->ForceCursorPosition( true, m_menuCursor );
  689. // Display a copy of menu
  690. std::unique_ptr<ACTION_MENU> menu( m->Clone() );
  691. m_menuOwner = toolId;
  692. m_menuActive = true;
  693. if( wxWindow* frame = dynamic_cast<wxWindow*>( m_frame ) )
  694. frame->PopupMenu( menu.get() );
  695. // If a menu is canceled then notify tool
  696. if( menu->GetSelected() < 0 )
  697. {
  698. TOOL_EVENT evt( TC_COMMAND, TA_CHOICE_MENU_CHOICE, -1 );
  699. evt.SetHasPosition( false );
  700. evt.SetParameter( m );
  701. dispatchInternal( evt );
  702. }
  703. // Restore setting in case it was vetoed
  704. m_warpMouseAfterContextMenu = true;
  705. // Notify the tools that menu has been closed
  706. TOOL_EVENT evt( TC_COMMAND, TA_CHOICE_MENU_CLOSED );
  707. evt.SetHasPosition( false );
  708. evt.SetParameter( m );
  709. dispatchInternal( evt );
  710. m_menuActive = false;
  711. m_menuOwner = -1;
  712. // Restore cursor settings
  713. for( const std::pair<const TOOL_ID, std::optional<VECTOR2D>>& cursorSetting : m_cursorSettings )
  714. {
  715. auto it = m_toolIdIndex.find( cursorSetting.first );
  716. wxASSERT( it != m_toolIdIndex.end() );
  717. if( it == m_toolIdIndex.end() )
  718. continue;
  719. KIGFX::VC_SETTINGS& vc = it->second->vcSettings;
  720. vc.m_forceCursorPosition = (bool) cursorSetting.second;
  721. vc.m_forcedPosition = cursorSetting.second ? *cursorSetting.second : VECTOR2D( 0, 0 );
  722. }
  723. m_cursorSettings.clear();
  724. break;
  725. }
  726. }
  727. TOOL_MANAGER::ID_LIST::iterator TOOL_MANAGER::finishTool( TOOL_STATE* aState )
  728. {
  729. auto it = std::find( m_activeTools.begin(), m_activeTools.end(), aState->theTool->GetId() );
  730. if( !aState->Pop() )
  731. {
  732. // Deactivate the tool if there are no other contexts saved on the stack
  733. if( it != m_activeTools.end() )
  734. it = m_activeTools.erase( it );
  735. aState->idle = true;
  736. }
  737. if( aState == m_activeState )
  738. setActiveState( nullptr );
  739. // Set transitions to be ready for future TOOL_EVENTs
  740. TOOL_BASE* tool = aState->theTool;
  741. if( tool->GetType() == INTERACTIVE )
  742. static_cast<TOOL_INTERACTIVE*>( tool )->resetTransitions();
  743. return it;
  744. }
  745. bool TOOL_MANAGER::ProcessEvent( const TOOL_EVENT& aEvent )
  746. {
  747. // Once the tool manager is shutting down, don't start
  748. // activating more tools
  749. if( m_shuttingDown )
  750. return true;
  751. bool handled = processEvent( aEvent );
  752. TOOL_STATE* activeTool = GetCurrentToolState();
  753. if( activeTool )
  754. setActiveState( activeTool );
  755. if( m_view && m_view->IsDirty() )
  756. {
  757. if( GetToolHolder() )
  758. GetToolHolder()->RefreshCanvas();
  759. #if defined( __WXMAC__ )
  760. wxTheApp->ProcessPendingEvents(); // required for updating brightening behind a popup menu
  761. #endif
  762. }
  763. UpdateUI( aEvent );
  764. return handled;
  765. }
  766. void TOOL_MANAGER::ScheduleContextMenu( TOOL_BASE* aTool, ACTION_MENU* aMenu,
  767. CONTEXT_MENU_TRIGGER aTrigger )
  768. {
  769. TOOL_STATE* st = m_toolState[aTool];
  770. st->contextMenu = aMenu;
  771. st->contextMenuTrigger = aTrigger;
  772. }
  773. bool TOOL_MANAGER::SaveClipboard( const std::string& aTextUTF8 )
  774. {
  775. wxLogNull doNotLog; // disable logging of failed clipboard actions
  776. if( wxTheClipboard->Open() )
  777. {
  778. // Store the UTF8 string as Unicode string in clipboard:
  779. wxTheClipboard->SetData( new wxTextDataObject( wxString( aTextUTF8.c_str(),
  780. wxConvUTF8 ) ) );
  781. wxTheClipboard->Flush(); // Allow data to be available after closing KiCad
  782. wxTheClipboard->Close();
  783. return true;
  784. }
  785. return false;
  786. }
  787. std::string TOOL_MANAGER::GetClipboardUTF8() const
  788. {
  789. std::string result;
  790. wxLogNull doNotLog; // disable logging of failed clipboard actions
  791. if( wxTheClipboard->Open() )
  792. {
  793. if( wxTheClipboard->IsSupported( wxDF_TEXT )
  794. || wxTheClipboard->IsSupported( wxDF_UNICODETEXT ) )
  795. {
  796. wxTextDataObject data;
  797. wxTheClipboard->GetData( data );
  798. // The clipboard is expected containing a Unicode string, so return it
  799. // as UTF8 string
  800. result = data.GetText().utf8_str();
  801. }
  802. wxTheClipboard->Close();
  803. }
  804. return result;
  805. }
  806. const KIGFX::VC_SETTINGS& TOOL_MANAGER::GetCurrentToolVC() const
  807. {
  808. if( TOOL_STATE* active = GetCurrentToolState() )
  809. return active->vcSettings;
  810. return m_viewControls->GetSettings();
  811. }
  812. TOOL_ID TOOL_MANAGER::MakeToolId( const std::string& aToolName )
  813. {
  814. static int currentId;
  815. return currentId++;
  816. }
  817. void TOOL_MANAGER::SetEnvironment( EDA_ITEM* aModel, KIGFX::VIEW* aView,
  818. KIGFX::VIEW_CONTROLS* aViewControls,
  819. APP_SETTINGS_BASE* aSettings, TOOLS_HOLDER* aFrame )
  820. {
  821. m_model = aModel;
  822. m_view = aView;
  823. m_viewControls = aViewControls;
  824. m_frame = aFrame;
  825. m_settings = aSettings;
  826. }
  827. bool TOOL_MANAGER::isActive( TOOL_BASE* aTool ) const
  828. {
  829. if( !isRegistered( aTool ) )
  830. return false;
  831. // Just check if the tool is on the active tools stack
  832. return alg::contains( m_activeTools, aTool->GetId() );
  833. }
  834. void TOOL_MANAGER::saveViewControls( TOOL_STATE* aState )
  835. {
  836. aState->vcSettings = m_viewControls->GetSettings();
  837. if( m_menuActive )
  838. {
  839. // Context menu is active, so the cursor settings are overridden (see DispatchContextMenu())
  840. auto it = m_cursorSettings.find( aState->theTool->GetId() );
  841. if( it != m_cursorSettings.end() )
  842. {
  843. const KIGFX::VC_SETTINGS& curr = m_viewControls->GetSettings();
  844. // Tool has overridden the cursor position, so store the new settings
  845. if( !curr.m_forceCursorPosition || curr.m_forcedPosition != m_menuCursor )
  846. {
  847. if( !curr.m_forceCursorPosition )
  848. it->second = std::nullopt;
  849. else
  850. it->second = curr.m_forcedPosition;
  851. }
  852. else
  853. {
  854. std::optional<VECTOR2D> cursor = it->second;
  855. if( cursor )
  856. {
  857. aState->vcSettings.m_forceCursorPosition = true;
  858. aState->vcSettings.m_forcedPosition = *cursor;
  859. }
  860. else
  861. {
  862. aState->vcSettings.m_forceCursorPosition = false;
  863. }
  864. }
  865. }
  866. }
  867. }
  868. void TOOL_MANAGER::applyViewControls( const TOOL_STATE* aState )
  869. {
  870. m_viewControls->ApplySettings( aState->vcSettings );
  871. }
  872. bool TOOL_MANAGER::processEvent( const TOOL_EVENT& aEvent )
  873. {
  874. wxLogTrace( kicadTraceToolStack, wxS( "TOOL_MANAGER::processEvent - %s" ), aEvent.Format() );
  875. // First try to dispatch the action associated with the event if it is a key press event
  876. bool handled = DispatchHotKey( aEvent );
  877. if( !handled )
  878. {
  879. TOOL_EVENT mod_event( aEvent );
  880. // Only immediate actions get the position. Otherwise clear for tool activation
  881. if( GetToolHolder() && !GetToolHolder()->GetDoImmediateActions() )
  882. {
  883. // An tool-selection-event has no position
  884. if( !mod_event.getCommandStr().empty()
  885. && mod_event.getCommandStr() != GetToolHolder()->CurrentToolName()
  886. && !mod_event.ForceImmediate() )
  887. {
  888. mod_event.SetHasPosition( false );
  889. }
  890. }
  891. // If the event is not handled through a hotkey activation, pass it to the currently
  892. // running tool loops
  893. handled |= dispatchInternal( mod_event );
  894. handled |= dispatchActivation( mod_event );
  895. // Open the context menu if requested by a tool
  896. DispatchContextMenu( mod_event );
  897. // Dispatch any remaining events in the event queue
  898. while( !m_eventQueue.empty() )
  899. {
  900. TOOL_EVENT event = m_eventQueue.front();
  901. m_eventQueue.pop_front();
  902. processEvent( event );
  903. }
  904. }
  905. wxLogTrace( kicadTraceToolStack, wxS( "TOOL_MANAGER::processEvent - %s handle event: %s" ),
  906. ( handled ? "Did" : "Did not" ), aEvent.Format() );
  907. return handled;
  908. }
  909. void TOOL_MANAGER::setActiveState( TOOL_STATE* aState )
  910. {
  911. if( m_activeState && m_viewControls )
  912. saveViewControls( m_activeState );
  913. m_activeState = aState;
  914. if( m_activeState && m_viewControls )
  915. applyViewControls( aState );
  916. }
  917. bool TOOL_MANAGER::IsToolActive( TOOL_ID aId ) const
  918. {
  919. auto it = m_toolIdIndex.find( aId );
  920. return !it->second->idle;
  921. }
  922. void TOOL_MANAGER::UpdateUI( const TOOL_EVENT& aEvent )
  923. {
  924. EDA_BASE_FRAME* frame = dynamic_cast<EDA_BASE_FRAME*>( GetToolHolder() );
  925. if( frame )
  926. frame->UpdateStatusBar();
  927. }