KiCad PCB EDA Suite
Loading...
Searching...
No Matches
action_manager.cpp
Go to the documentation of this file.
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 The KiCad Developers, see AUTHORS.txt for contributors.
6 * @author Maciej Suminski <[email protected]>
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
26#include <eda_draw_frame.h>
27#include <tool/action_manager.h>
28#include <tool/tool_action.h>
29#include <tool/tool_manager.h>
30#include <trace_helpers.h>
31#include <wx/log.h>
32
33#include <hotkeys_basic.h>
34#include <cctype>
35
36
38 m_toolMgr( aToolManager )
39{
40 // Register known actions
41 std::list<TOOL_ACTION*>& actionList = GetActionList();
42
43 for( TOOL_ACTION* action : actionList )
44 {
45 if( action->m_id == -1 )
46 action->m_id = MakeActionId( action->m_name );
47
48 int groupID = 0;
49 std::string groupName = "none";
50
51 std::optional<TOOL_ACTION_GROUP> group = action->GetActionGroup();
52
53 if( group.has_value() )
54 {
55 groupID = group.value().GetGroupID();
56 groupName = group.value().GetName();
57 }
58
59 wxLogTrace( kicadTraceToolStack,
60 "ACTION_MANAGER::ACTION_MANAGER: Registering action %s with ID %d, UI ID %d, "
61 "group %s(%d), toolbar state %s",
62 action->m_name, action->m_id, action->GetUIId(), groupName, groupID,
63 action->m_toolbarState.to_string() );
64
65 RegisterAction( action );
66 }
67}
68
69
73
74
76{
77 // TOOL_ACTIONs are supposed to be named [appName.]toolName.actionName (with dots between)
78 // action name without specifying at least toolName is not valid
79 wxASSERT( aAction->GetName().find( '.', 0 ) != std::string::npos );
80
81 // TOOL_ACTIONs must have unique names & ids
82 wxASSERT_MSG( m_actionNameIndex.find( aAction->m_name ) == m_actionNameIndex.end(),
83 wxString::Format( "Action '%s' already registered", aAction->m_name ) );
84
85 m_actionNameIndex[aAction->m_name] = aAction;
86
87 if( aAction->HasCustomUIId() )
88 m_customUIIdIndex[aAction->GetUIId()] = aAction;
89}
90
91
93 const ACTION_CONDITIONS& aConditions )
94{
95 // Remove any existing handlers with the old conditions to ensure the UI layer doesn't have
96 // stale data.
97 if( m_toolMgr )
98 m_toolMgr->GetToolHolder()->UnregisterUIUpdateHandler( aAction );
99
100 m_uiConditions[aAction.GetId()] = aConditions;
101
102 wxLogTrace( kicadTraceToolStack,
103 wxS( "ACTION_MANAGER::SetConditions: Registering conditions for ID %d - %s" ),
104 aAction.GetId(), aAction.GetName() );
105
106 // Register a new handler with the new conditions
107 if( m_toolMgr )
108 m_toolMgr->GetToolHolder()->RegisterUIUpdateHandler( aAction, aConditions );
109}
110
111
113{
114 const auto it = m_uiConditions.find( aAction.GetId() );
115
116 // If the action doesn't have something registered, then return null
117 if( it == m_uiConditions.end() )
118 return nullptr;
119 else
120 return &it->second;
121}
122
123
124int ACTION_MANAGER::MakeActionId( const std::string& aActionName )
125{
126 static int currentActionId = 1;
127
128 return currentActionId++;
129}
130
131
132TOOL_ACTION* ACTION_MANAGER::FindAction( const std::string& aActionName ) const
133{
134 std::map<std::string, TOOL_ACTION*>::const_iterator it = m_actionNameIndex.find( aActionName );
135
136 if( it != m_actionNameIndex.end() )
137 return it->second;
138
139 return nullptr;
140}
141
142
143bool ACTION_MANAGER::RunHotKey( int aHotKey ) const
144{
145 int key = aHotKey & ~MD_MODIFIER_MASK;
146 int mod = aHotKey & MD_MODIFIER_MASK;
147
148 if( key >= 'a' && key <= 'z' )
149 key = std::toupper( key );
150
151 wxLogTrace( kicadTraceToolStack, wxS( "ACTION_MANAGER::RunHotKey Key: %s" ),
152 KeyNameFromKeyCode( aHotKey ) );
153
154 HOTKEY_LIST::const_iterator it = m_actionHotKeys.find( key | mod );
155
156 // If no luck, try without Shift, to handle keys that require it
157 // e.g. to get ? you need to press Shift+/ without US keyboard layout
158 // Hardcoding ? as Shift+/ is a bad idea, as on another layout you may need to press a
159 // different combination.
160 // This doesn't apply for letters, as we already handled case normalisation.
161 if( it == m_actionHotKeys.end() && !std::isalpha( key ) )
162 {
163 wxLogTrace( kicadTraceToolStack,
164 wxS( "ACTION_MANAGER::RunHotKey No actions found, searching with key: %s" ),
165 KeyNameFromKeyCode( key | ( mod & ~MD_SHIFT ) ) );
166
167 it = m_actionHotKeys.find( key | ( mod & ~MD_SHIFT ) );
168 }
169
170 // Still no luck, we're done without a match
171 if( it == m_actionHotKeys.end() )
172 return false; // no appropriate action found for the hotkey
173
174 const std::list<TOOL_ACTION*>& actions = it->second;
175
176 // Choose the action that has the highest priority on the active tools stack
177 // If there is none, run the global action associated with the hot key
178 int highestPriority = -1, priority = -1;
179 const TOOL_ACTION* context = nullptr; // pointer to context action of the highest priority tool
180 std::vector<const TOOL_ACTION*> global; // pointers to global actions
181 // if there is no context action
182
183 for( const TOOL_ACTION* action : actions )
184 {
185 if( action->GetScope() == AS_GLOBAL )
186 {
187 // Store the global action in case there are no context actions to run
188 global.emplace_back( action );
189 continue;
190 }
191
192 TOOL_BASE* tool = m_toolMgr->FindTool( action->GetToolName() );
193
194 if( tool )
195 {
196 // Choose the action that goes to the tool with highest priority
197 // (i.e. is on the top of active tools stack)
198 priority = m_toolMgr->GetPriority( tool->GetId() );
199
200 if( priority >= 0 && priority > highestPriority )
201 {
202 highestPriority = priority;
203 context = action;
204 }
205 }
206 }
207
208 // Get the selection to use to test if the action is enabled
209 SELECTION& sel = m_toolMgr->GetToolHolder()->GetCurrentSelection();
210
211 if( context )
212 {
213 bool runAction = true;
214
215 if( const ACTION_CONDITIONS* aCond = GetCondition( *context ) )
216 runAction = aCond->enableCondition( sel );
217
218 wxLogTrace( kicadTraceToolStack,
219 wxS( "ACTION_MANAGER::RunHotKey %s context action: %s for hotkey %s" ),
220 runAction ? wxS( "Running" ) : wxS( "Not running" ),
221 context->GetName(),
222 KeyNameFromKeyCode( aHotKey ) );
223
224 if( runAction )
225 return m_toolMgr->RunAction( *context );
226 }
227 else if( !global.empty() )
228 {
229 for( const TOOL_ACTION* act : global )
230 {
231 bool runAction = true;
232
233 if( const ACTION_CONDITIONS* aCond = GetCondition( *act ) )
234 runAction = aCond->enableCondition( sel );
235
236 wxLogTrace( kicadTraceToolStack,
237 wxS( "ACTION_MANAGER::RunHotKey %s global action: %s for hotkey %s" ),
238 runAction ? wxS( "Running" ) : wxS( "Not running" ),
239 act->GetName(),
240 KeyNameFromKeyCode( aHotKey ) );
241
242 if( runAction && m_toolMgr->RunAction( *act ) )
243 return true;
244 }
245 }
246
247 wxLogTrace( kicadTraceToolStack,
248 wxS( "ACTION_MANAGER::RunHotKey No action found for key %s" ),
249 KeyNameFromKeyCode( aHotKey ) );
250
251 return false;
252}
253
254
255bool ACTION_MANAGER::IsActionUIId( int aId ) const
256{
257 // Automatically assigned IDs are always in this range
258 if( aId >= TOOL_ACTION::GetBaseUIId() )
259 return true;
260
261 // Search the custom assigned UI IDs
262 auto it = m_customUIIdIndex.find( aId );
263
264 return ( it != m_customUIIdIndex.end() );
265}
266
267
268const std::map<std::string, TOOL_ACTION*>& ACTION_MANAGER::GetActions() const
269{
270 return m_actionNameIndex;
271}
272
273
274int ACTION_MANAGER::GetHotKey( const TOOL_ACTION& aAction ) const
275{
276 std::map<int, int>::const_iterator it = m_hotkeys.find( aAction.GetId() );
277
278 if( it == m_hotkeys.end() )
279 return 0;
280
281 return it->second;
282}
283
284
285void ACTION_MANAGER::UpdateHotKeys( bool aFullUpdate )
286{
287 static std::map<std::string, int> legacyHotKeyMap;
288 static std::map<std::string, std::pair<int, int>> userHotKeyMap;
289 static bool mapsInitialized = false;
290
291 m_actionHotKeys.clear();
292 m_hotkeys.clear();
293
294 if( m_toolMgr->GetToolHolder() && ( aFullUpdate || !mapsInitialized ) )
295 {
296 ReadLegacyHotkeyConfig( m_toolMgr->GetToolHolder()->ConfigBaseName(), legacyHotKeyMap );
297 ReadHotKeyConfig( wxEmptyString, userHotKeyMap );
298 mapsInitialized = true;
299 }
300
301 for( const auto& ii : m_actionNameIndex )
302 {
303 TOOL_ACTION* action = ii.second;
304 int hotkey = 0;
305 int alt = 0;
306
307 if( aFullUpdate )
308 processHotKey( action, legacyHotKeyMap, userHotKeyMap );
309
310 hotkey = action->GetHotKey();
311 alt = action->GetHotKeyAlt();
312
313 if( hotkey > 0 )
314 m_actionHotKeys[hotkey].push_back( action );
315
316 if( alt > 0 )
317 m_actionHotKeys[alt].push_back( action );
318
319 m_hotkeys[action->GetId()] = hotkey;
320 }
321}
322
323
325 const std::map<std::string, int>& aLegacyMap,
326 const std::map<std::string, std::pair<int, int>>& aHotKeyMap )
327{
328 aAction->m_hotKey = aAction->m_defaultHotKey;
329
330 if( !aAction->m_legacyName.empty() && aLegacyMap.count( aAction->m_legacyName ) )
331 aAction->SetHotKey( aLegacyMap.at( aAction->m_legacyName ) );
332
333 if( aHotKeyMap.count( aAction->m_name ) )
334 {
335 std::pair<int, int> keys = aHotKeyMap.at( aAction->m_name );
336 aAction->SetHotKey( keys.first, keys.second );
337 }
338}
~ACTION_MANAGER()
Unregister every registered action.
HOTKEY_LIST m_actionHotKeys
Quick action<->hot key lookup.
const ACTION_CONDITIONS * GetCondition(const TOOL_ACTION &aAction) const
Get the conditions to use for a specific tool action.
std::map< int, TOOL_ACTION * > m_customUIIdIndex
Map for indexing actions by their hotkeys.
TOOL_MANAGER * m_toolMgr
Map for indexing actions by their names.
void processHotKey(TOOL_ACTION *aAction, const std::map< std::string, int > &aLegacyMap, const std::map< std::string, std::pair< int, int > > &aHotKeyMap)
Tool manager needed to run actions.
const std::map< std::string, TOOL_ACTION * > & GetActions() const
Get a list of currently-registered actions mapped by their name.
std::map< std::string, TOOL_ACTION * > m_actionNameIndex
Map for recording actions that have custom UI IDs.
std::map< int, int > m_hotkeys
bool RunHotKey(int aHotKey) const
Run an action associated with a hotkey (if there is one available).
void SetConditions(const TOOL_ACTION &aAction, const ACTION_CONDITIONS &aConditions)
Set the conditions the UI elements for activating a specific tool action should use for determining t...
void RegisterAction(TOOL_ACTION *aAction)
Add a tool action to the manager and sets it up.
TOOL_ACTION * FindAction(const std::string &aActionName) const
Find an action with a given name (if there is one available).
static std::list< TOOL_ACTION * > & GetActionList()
Return list of TOOL_ACTIONs.
bool IsActionUIId(int aId) const
Test if a UI ID corresponds to an action ID in our system.
int GetHotKey(const TOOL_ACTION &aAction) const
Return the hot key associated with a given action or 0 if there is none.
static int MakeActionId(const std::string &aActionName)
Generate an unique ID from for an action with given name.
std::map< int, ACTION_CONDITIONS > m_uiConditions
Map the command ID that wx uses for the action to the UI conditions for the menu/toolbar items.
void UpdateHotKeys(bool aFullUpdate)
Optionally read the hotkey config files and then rebuilds the internal hotkey maps.
ACTION_MANAGER(TOOL_MANAGER *aToolManager)
Represent a single user action.
bool HasCustomUIId() const
Return true if this action has a custom UI ID set.
static int GetBaseUIId()
Get the base value used to offset the user interface IDs for the actions.
void SetHotKey(int aKeycode, int aKeycodeAlt=0)
int GetId() const
Return the unique id of the TOOL_ACTION object.
const std::string & GetName() const
Return name of the action.
int GetHotKeyAlt() const
int GetHotKey() const
Return the hotkey keycode which initiates the action.
const int m_defaultHotKey
Default hot key.
int m_hotKey
The current hotkey (post-user-settings-application).
const std::string m_legacyName
Name for reading legacy hotkey settings.
std::string m_name
Name of the action (convention is "app.tool.actionName")
int GetUIId() const
Get the unique ID for this action in the user interface system.
Base abstract interface for all kinds of tools.
Definition tool_base.h:66
TOOL_ID GetId() const
Return the unique identifier of the tool.
Definition tool_base.h:123
Master controller class:
const wxChar *const kicadTraceToolStack
Flag to enable tracing of the tool handling stack.
void ReadHotKeyConfig(const wxString &aFileName, std::map< std::string, std::pair< int, int > > &aHotKeys)
Read a hotkey config file into a map.
int ReadLegacyHotkeyConfig(const wxString &aAppname, std::map< std::string, int > &aMap)
Read configuration data and fill the current hotkey list with hotkeys.
wxString KeyNameFromKeyCode(int aKeycode, bool *aIsFound)
Return the key name from the key code.
Functors that can be used to figure out how the action controls should be displayed in the UI and if ...
@ AS_GLOBAL
Global action (toolbar/main menu event, global shortcut)
Definition tool_action.h:49
@ MD_MODIFIER_MASK
Definition tool_event.h:149
@ MD_SHIFT
Definition tool_event.h:143
wxLogTrace helper definitions.