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.

1188 lines
33 KiB

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