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.

292 lines
9.1 KiB

  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2013 CERN
  5. * Copyright (C) 2019-2021 KiCad Developers, see AUTHORS.txt for contributors.
  6. * @author Maciej Suminski <maciej.suminski@cern.ch>
  7. *
  8. * This program is free software; you can redistribute it and/or
  9. * modify it under the terms of the GNU General Public License
  10. * as published by the Free Software Foundation; either version 2
  11. * of the License, or (at your option) any later version.
  12. *
  13. * This program is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU General Public License
  19. * along with this program; if not, you may find one here:
  20. * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  21. * or you may search the http://www.gnu.org website for the version 2 license,
  22. * or you may write to the Free Software Foundation, Inc.,
  23. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  24. */
  25. #include <eda_draw_frame.h>
  26. #include <tool/action_manager.h>
  27. #include <tool/tool_action.h>
  28. #include <tool/tool_manager.h>
  29. #include <trace_helpers.h>
  30. #include <wx/log.h>
  31. #include <hotkeys_basic.h>
  32. #include <cctype>
  33. ACTION_MANAGER::ACTION_MANAGER( TOOL_MANAGER* aToolManager ) :
  34. m_toolMgr( aToolManager )
  35. {
  36. // Register known actions
  37. std::list<TOOL_ACTION*>& actionList = GetActionList();
  38. for( TOOL_ACTION* action : actionList )
  39. {
  40. if( action->m_id == -1 )
  41. action->m_id = MakeActionId( action->m_name );
  42. RegisterAction( action );
  43. }
  44. }
  45. ACTION_MANAGER::~ACTION_MANAGER()
  46. {
  47. }
  48. void ACTION_MANAGER::RegisterAction( TOOL_ACTION* aAction )
  49. {
  50. // TOOL_ACTIONs are supposed to be named [appName.]toolName.actionName (with dots between)
  51. // action name without specifying at least toolName is not valid
  52. wxASSERT( aAction->GetName().find( '.', 0 ) != std::string::npos );
  53. // TOOL_ACTIONs must have unique names & ids
  54. wxASSERT( m_actionNameIndex.find( aAction->m_name ) == m_actionNameIndex.end() );
  55. m_actionNameIndex[aAction->m_name] = aAction;
  56. }
  57. void ACTION_MANAGER::SetConditions( const TOOL_ACTION& aAction,
  58. const ACTION_CONDITIONS& aConditions )
  59. {
  60. // Remove any existing handlers with the old conditions to ensure the UI layer doesn't have
  61. // stale data.
  62. if( m_toolMgr )
  63. m_toolMgr->GetToolHolder()->UnregisterUIUpdateHandler( aAction );
  64. m_uiConditions[aAction.GetId()] = aConditions;
  65. wxLogTrace( kicadTraceToolStack,
  66. "ACTION_MANAGER::SetConditions: Registering conditions for ID %d - %s",
  67. aAction.GetId(), aAction.GetName() );
  68. // Register a new handler with the new conditions
  69. if( m_toolMgr )
  70. m_toolMgr->GetToolHolder()->RegisterUIUpdateHandler( aAction, aConditions );
  71. }
  72. const ACTION_CONDITIONS* ACTION_MANAGER::GetCondition( const TOOL_ACTION& aAction ) const
  73. {
  74. const auto it = m_uiConditions.find( aAction.GetId() );
  75. // If the action doesn't have something registered, then return null
  76. if( it == m_uiConditions.end() )
  77. return nullptr;
  78. else
  79. return &it->second;
  80. }
  81. int ACTION_MANAGER::MakeActionId( const std::string& aActionName )
  82. {
  83. static int currentActionId = 1;
  84. return currentActionId++;
  85. }
  86. TOOL_ACTION* ACTION_MANAGER::FindAction( const std::string& aActionName ) const
  87. {
  88. std::map<std::string, TOOL_ACTION*>::const_iterator it = m_actionNameIndex.find( aActionName );
  89. if( it != m_actionNameIndex.end() )
  90. return it->second;
  91. return nullptr;
  92. }
  93. bool ACTION_MANAGER::RunHotKey( int aHotKey ) const
  94. {
  95. int key = aHotKey & ~MD_MODIFIER_MASK;
  96. int mod = aHotKey & MD_MODIFIER_MASK;
  97. if( key >= 'a' && key <= 'z' )
  98. key = std::toupper( key );
  99. wxLogTrace( kicadTraceToolStack, "ACTION_MANAGER::RunHotKey Key: %s",
  100. KeyNameFromKeyCode( aHotKey ) );
  101. HOTKEY_LIST::const_iterator it = m_actionHotKeys.find( key | mod );
  102. // If no luck, try without Shift, to handle keys that require it
  103. // e.g. to get ? you need to press Shift+/ without US keyboard layout
  104. // Hardcoding ? as Shift+/ is a bad idea, as on another layout you may need to press a
  105. // different combination
  106. if( it == m_actionHotKeys.end() )
  107. {
  108. wxLogTrace( kicadTraceToolStack,
  109. "ACTION_MANAGER::RunHotKey No actions found, searching with key: %s",
  110. KeyNameFromKeyCode( key | ( mod & ~MD_SHIFT ) ) );
  111. it = m_actionHotKeys.find( key | ( mod & ~MD_SHIFT ) );
  112. if( it == m_actionHotKeys.end() )
  113. return false; // no appropriate action found for the hotkey
  114. }
  115. const std::list<TOOL_ACTION*>& actions = it->second;
  116. // Choose the action that has the highest priority on the active tools stack
  117. // If there is none, run the global action associated with the hot key
  118. int highestPriority = -1, priority = -1;
  119. const TOOL_ACTION* context = nullptr; // pointer to context action of the highest priority tool
  120. std::vector<const TOOL_ACTION*> global; // pointers to global actions
  121. // if there is no context action
  122. for( const TOOL_ACTION* action : actions )
  123. {
  124. if( action->GetScope() == AS_GLOBAL )
  125. {
  126. // Store the global action in case there are no context actions to run
  127. global.emplace_back( action );
  128. continue;
  129. }
  130. TOOL_BASE* tool = m_toolMgr->FindTool( action->GetToolName() );
  131. if( tool )
  132. {
  133. // Choose the action that goes to the tool with highest priority
  134. // (i.e. is on the top of active tools stack)
  135. priority = m_toolMgr->GetPriority( tool->GetId() );
  136. if( priority >= 0 && priority > highestPriority )
  137. {
  138. highestPriority = priority;
  139. context = action;
  140. }
  141. }
  142. }
  143. // Get the selection to use to test if the action is enabled
  144. SELECTION& sel = m_toolMgr->GetToolHolder()->GetCurrentSelection();
  145. if( context )
  146. {
  147. bool runAction = true;
  148. if( const ACTION_CONDITIONS* aCond = GetCondition( *context ) )
  149. runAction = aCond->enableCondition( sel );
  150. wxLogTrace( kicadTraceToolStack,
  151. "ACTION_MANAGER::RunHotKey %s context action: %s for hotkey %s",
  152. runAction ? "Running" : "Not running",
  153. context->GetName(),
  154. KeyNameFromKeyCode( aHotKey ) );
  155. if( runAction )
  156. return m_toolMgr->RunAction( *context, true );
  157. }
  158. else if( !global.empty() )
  159. {
  160. for( auto act : global )
  161. {
  162. bool runAction = true;
  163. if( const ACTION_CONDITIONS* aCond = GetCondition( *act ) )
  164. runAction = aCond->enableCondition( sel );
  165. wxLogTrace( kicadTraceToolStack,
  166. "ACTION_MANAGER::RunHotKey %s global action: %s for hotkey %s",
  167. runAction ? "Running" : "Not running",
  168. act->GetName(),
  169. KeyNameFromKeyCode( aHotKey ) );
  170. if( runAction && m_toolMgr->RunAction( *act, true ) )
  171. return true;
  172. }
  173. }
  174. wxLogTrace( kicadTraceToolStack,
  175. "ACTION_MANAGER::RunHotKey No action found for key %s",
  176. KeyNameFromKeyCode( aHotKey ) );
  177. return false;
  178. }
  179. const std::map<std::string, TOOL_ACTION*>& ACTION_MANAGER::GetActions() const
  180. {
  181. return m_actionNameIndex;
  182. }
  183. int ACTION_MANAGER::GetHotKey( const TOOL_ACTION& aAction ) const
  184. {
  185. std::map<int, int>::const_iterator it = m_hotkeys.find( aAction.GetId() );
  186. if( it == m_hotkeys.end() )
  187. return 0;
  188. return it->second;
  189. }
  190. void ACTION_MANAGER::UpdateHotKeys( bool aFullUpdate )
  191. {
  192. std::map<std::string, int> legacyHotKeyMap;
  193. std::map<std::string, int> userHotKeyMap;
  194. m_actionHotKeys.clear();
  195. m_hotkeys.clear();
  196. if( aFullUpdate && m_toolMgr->GetToolHolder() )
  197. {
  198. ReadLegacyHotkeyConfig( m_toolMgr->GetToolHolder()->ConfigBaseName(), legacyHotKeyMap );
  199. ReadHotKeyConfig( wxEmptyString, userHotKeyMap );
  200. }
  201. for( const auto& ii : m_actionNameIndex )
  202. {
  203. TOOL_ACTION* action = ii.second;
  204. int hotkey = 0;
  205. if( aFullUpdate )
  206. hotkey = processHotKey( action, legacyHotKeyMap, userHotKeyMap );
  207. else
  208. hotkey = action->GetHotKey();
  209. if( hotkey > 0 )
  210. m_actionHotKeys[hotkey].push_back( action );
  211. m_hotkeys[action->GetId()] = hotkey;
  212. }
  213. }
  214. int ACTION_MANAGER::processHotKey( TOOL_ACTION* aAction, std::map<std::string, int> aLegacyMap,
  215. std::map<std::string, int> aHotKeyMap )
  216. {
  217. aAction->m_hotKey = aAction->m_defaultHotKey;
  218. if( !aAction->m_legacyName.empty() && aLegacyMap.count( aAction->m_legacyName ) )
  219. aAction->SetHotKey( aLegacyMap[ aAction->m_legacyName ] );
  220. if( aHotKeyMap.count( aAction->m_name ) )
  221. aAction->SetHotKey( aHotKeyMap[ aAction->m_name ] );
  222. return aAction->m_hotKey;
  223. }