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.

1259 lines
36 KiB

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