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, see <https://www.gnu.org/licenses/>.
20 */
21
22#include <eda_draw_frame.h>
23#include <tool/action_manager.h>
24#include <tool/tool_action.h>
25#include <tool/tool_manager.h>
26#include <trace_helpers.h>
27#include <wx/log.h>
28
29#include <hotkeys_basic.h>
30#include <algorithm>
31#include <cctype>
32
33
35 m_toolMgr( aToolManager )
36{
37 // Register known actions
38 std::list<TOOL_ACTION*>& actionList = GetActionList();
39
40 for( TOOL_ACTION* action : actionList )
41 {
42 if( action->m_id == -1 )
43 action->m_id = MakeActionId( action->m_name );
44
45 int groupID = 0;
46 std::string groupName = "none";
47
48 std::optional<TOOL_ACTION_GROUP> group = action->GetActionGroup();
49
50 if( group.has_value() )
51 {
52 groupID = group.value().GetGroupID();
53 groupName = group.value().GetName();
54 }
55
56 wxLogTrace( kicadTraceToolStack, "ACTION_MANAGER::ACTION_MANAGER: Registering action %s with ID %d, UI ID %d, "
57 "group %s(%d), toolbar state %s",
58 action->m_name, action->m_id, action->GetUIId(), groupName, groupID,
59 action->m_toolbarState.to_string() );
60
61 RegisterAction( action );
62 }
63}
64
65
69
70
72{
73 // TOOL_ACTIONs are supposed to be named [appName.]toolName.actionName (with dots between)
74 // action name without specifying at least toolName is not valid
75 wxASSERT( aAction->GetName().find( '.', 0 ) != std::string::npos );
76
77 // TOOL_ACTIONs must have unique names & ids
78 wxASSERT_MSG( m_actionNameIndex.find( aAction->m_name ) == m_actionNameIndex.end(),
79 wxString::Format( "Action '%s' already registered", aAction->m_name ) );
80
81 m_actionNameIndex[aAction->m_name] = aAction;
82
83 if( aAction->HasCustomUIId() )
84 m_customUIIdIndex[aAction->GetUIId()] = aAction;
85}
86
87
88void ACTION_MANAGER::SetConditions( const TOOL_ACTION& aAction, const ACTION_CONDITIONS& aConditions )
89{
90 // Remove any existing handlers with the old conditions to ensure the UI layer doesn't have
91 // stale data.
92 if( m_toolMgr )
93 m_toolMgr->GetToolHolder()->UnregisterUIUpdateHandler( aAction );
94
95 m_uiConditions[aAction.GetId()] = aConditions;
96
97 wxLogTrace( kicadTraceToolStack, wxS( "ACTION_MANAGER::SetConditions: Registering conditions for ID %d - %s" ),
98 aAction.GetId(), aAction.GetName() );
99
100 // Register a new handler with the new conditions
101 if( m_toolMgr )
102 m_toolMgr->GetToolHolder()->RegisterUIUpdateHandler( aAction, aConditions );
103}
104
105
107{
108 const auto it = m_uiConditions.find( aAction.GetId() );
109
110 // If the action doesn't have something registered, then return null
111 if( it == m_uiConditions.end() )
112 return nullptr;
113 else
114 return &it->second;
115}
116
117
118int ACTION_MANAGER::MakeActionId( const std::string& aActionName )
119{
120 static int currentActionId = 1;
121
122 return currentActionId++;
123}
124
125
126TOOL_ACTION* ACTION_MANAGER::FindAction( const std::string& aActionName ) const
127{
128 std::map<std::string, TOOL_ACTION*>::const_iterator it = m_actionNameIndex.find( aActionName );
129
130 if( it != m_actionNameIndex.end() )
131 return it->second;
132
133 return nullptr;
134}
135
136
137bool ACTION_MANAGER::RunHotKey( int aHotKey ) const
138{
139 int key = aHotKey & ~MD_MODIFIER_MASK;
140 int mod = aHotKey & MD_MODIFIER_MASK;
141
142 if( key >= 'a' && key <= 'z' )
143 key = std::toupper( key );
144
145 wxLogTrace( kicadTraceToolStack, wxS( "ACTION_MANAGER::RunHotKey Key: %s" ), KeyNameFromKeyCode( aHotKey ) );
146
147 int matchedHotKey = key | mod;
148 auto it = m_actionHotKeys.find( matchedHotKey );
149
150 // If no luck, try without Shift, to handle keys that require it
151 // e.g. to get ? you need to press Shift+/ without US keyboard layout
152 // Hardcoding ? as Shift+/ is a bad idea, as on another layout you may need to press a
153 // different combination.
154 // This doesn't apply for letters, as we already handled case normalisation.
155 if( it == m_actionHotKeys.end() && !std::isalpha( key ) )
156 {
157 matchedHotKey = key | ( mod & ~MD_SHIFT );
158
159 wxLogTrace( kicadTraceToolStack, wxS( "ACTION_MANAGER::RunHotKey No actions found, searching with key: %s" ),
160 KeyNameFromKeyCode( matchedHotKey ) );
161
162 it = m_actionHotKeys.find( matchedHotKey );
163 }
164
165 // Still no luck, we're done without a match
166 if( it == m_actionHotKeys.end() )
167 return false; // no appropriate action found for the hotkey
168
169 const std::list<TOOL_ACTION*>& actions = it->second;
170
171 // Choose the action that has the highest priority on the active tools stack
172 // If there is none, run the global action associated with the hot key
173 int highestPriority = -1;
174 int priority = -1;
175 const TOOL_ACTION* context = nullptr; // pointer to context action of the highest priority tool
176 std::vector<const TOOL_ACTION*> global; // pointers to global actions if there are no context actions
177
178 for( const TOOL_ACTION* action : actions )
179 {
180 if( action->GetScope() == AS_GLOBAL )
181 {
182 // Store the global action in case there are no context actions to run
183 global.emplace_back( action );
184 continue;
185 }
186
187 TOOL_BASE* tool = m_toolMgr->FindTool( action->GetToolName() );
188
189 if( tool )
190 {
191 // Choose the action that goes to the tool with highest priority
192 // (i.e. is on the top of active tools stack)
193 priority = m_toolMgr->GetPriority( tool->GetId() );
194
195 if( priority >= 0 && priority > highestPriority )
196 {
197 highestPriority = priority;
198 context = action;
199 }
200 }
201 }
202
203 if( !context && global.size() > 1 )
204 {
205 if( EDA_BASE_FRAME* frame = dynamic_cast<EDA_BASE_FRAME*>( m_toolMgr->GetToolHolder() ) )
206 PromoteUserBoundFrameAction( global, frame->GetFrameType(), matchedHotKey );
207 }
208
209 // Get the selection to use to test if the action is enabled
210 SELECTION& sel = m_toolMgr->GetToolHolder()->GetCurrentSelection();
211
212 if( context )
213 {
214 bool runAction = true;
215
216 if( const ACTION_CONDITIONS* aCond = GetCondition( *context ) )
217 runAction = aCond->GetHotkeyCondition()( sel );
218
219 wxLogTrace( kicadTraceToolStack, wxS( "ACTION_MANAGER::RunHotKey %s context action: %s for hotkey %s" ),
220 runAction ? wxS( "Running" ) : wxS( "Not running" ),
221 context->GetName(), KeyNameFromKeyCode( aHotKey ) );
222
223 if( runAction )
224 return m_toolMgr->RunAction( *context );
225 }
226 else if( !global.empty() )
227 {
228 for( const TOOL_ACTION* act : global )
229 {
230 bool runAction = true;
231
232 if( const ACTION_CONDITIONS* aCond = GetCondition( *act ) )
233 runAction = aCond->GetHotkeyCondition()( sel );
234
235 wxLogTrace( kicadTraceToolStack, wxS( "ACTION_MANAGER::RunHotKey %s global action: %s for hotkey %s" ),
236 runAction ? wxS( "Running" ) : wxS( "Not running" ),
237 act->GetName(), KeyNameFromKeyCode( aHotKey ) );
238
239 if( runAction && m_toolMgr->RunAction( *act ) )
240 return true;
241 }
242 }
243
244 wxLogTrace( kicadTraceToolStack, wxS( "ACTION_MANAGER::RunHotKey No action found for key %s" ),
245 KeyNameFromKeyCode( aHotKey ) );
246
247 return false;
248}
249
250
252{
253 switch( aFrameType )
254 {
255 case FRAME_PCB_EDITOR:
258 return "pcbnew.";
259
260 case FRAME_SCH:
262 case FRAME_SCH_VIEWER:
263 case FRAME_SIMULATOR:
264 return "eeschema.";
265
266 case FRAME_CVPCB:
268 return "cvpcb.";
269
270 case FRAME_GERBER:
271 return "gerbview.";
272
273 case FRAME_PL_EDITOR:
274 return "plEditor.";
275
277 return "kicad.";
278
279 default:
280 return "";
281 }
282}
283
284
285void ACTION_MANAGER::PromoteUserBoundFrameAction( std::vector<const TOOL_ACTION*>& aGlobalActions, FRAME_T aFrameType,
286 int aMatchedHotKey )
287{
288 const std::string framePrefix = FrameNamespacePrefix( aFrameType );
289
290 if( framePrefix.empty() )
291 return;
292
293 auto userBoundInFrame = [&]( const TOOL_ACTION* aAction )
294 {
295 return aAction->GetName().starts_with( framePrefix ) && aAction->IsHotKeyUserBound( aMatchedHotKey );
296 };
297
298 if( std::any_of( aGlobalActions.begin(), aGlobalActions.end(), userBoundInFrame ) )
299 std::stable_partition( aGlobalActions.begin(), aGlobalActions.end(), userBoundInFrame );
300}
301
302
303bool ACTION_MANAGER::IsActionUIId( int aId ) const
304{
305 // Automatically assigned IDs are always in this range
306 if( aId >= TOOL_ACTION::GetBaseUIId() )
307 return true;
308
309 // Search the custom assigned UI IDs
310 auto it = m_customUIIdIndex.find( aId );
311
312 return ( it != m_customUIIdIndex.end() );
313}
314
315
316const std::map<std::string, TOOL_ACTION*>& ACTION_MANAGER::GetActions() const
317{
318 return m_actionNameIndex;
319}
320
321
322int ACTION_MANAGER::GetHotKey( const TOOL_ACTION& aAction ) const
323{
324 std::map<int, int>::const_iterator it = m_hotkeys.find( aAction.GetId() );
325
326 if( it == m_hotkeys.end() )
327 return 0;
328
329 return it->second;
330}
331
332
333void ACTION_MANAGER::UpdateHotKeys( bool aFullUpdate )
334{
335 static std::map<std::string, int> legacyHotKeyMap;
336 static std::map<std::string, std::pair<int, int>> userHotKeyMap;
337 static bool mapsInitialized = false;
338
339 m_actionHotKeys.clear();
340 m_hotkeys.clear();
341
342 if( m_toolMgr->GetToolHolder() && ( aFullUpdate || !mapsInitialized ) )
343 {
344 ReadLegacyHotkeyConfig( m_toolMgr->GetToolHolder()->ConfigBaseName(), legacyHotKeyMap );
345 ReadHotKeyConfig( wxEmptyString, userHotKeyMap );
346 mapsInitialized = true;
347 }
348
349 for( const auto& ii : m_actionNameIndex )
350 {
351 TOOL_ACTION* action = ii.second;
352 int hotkey = 0;
353 int alt = 0;
354
355 if( aFullUpdate )
356 processHotKey( action, legacyHotKeyMap, userHotKeyMap );
357
358 hotkey = action->GetHotKey();
359 alt = action->GetHotKeyAlt();
360
361 if( hotkey > 0 )
362 m_actionHotKeys[hotkey].push_back( action );
363
364 if( alt > 0 )
365 m_actionHotKeys[alt].push_back( action );
366
367 m_hotkeys[action->GetId()] = hotkey;
368 }
369}
370
371
373 const std::map<std::string, int>& aLegacyMap,
374 const std::map<std::string, std::pair<int, int>>& aHotKeyMap )
375{
376 int hotkey = aAction->m_defaultHotKey;
377 int alternate = aAction->m_defaultHotKeyAlt;
378
379 if( !aAction->m_legacyName.empty() && aLegacyMap.count( aAction->m_legacyName ) )
380 hotkey = aLegacyMap.at( aAction->m_legacyName );
381
382 if( aHotKeyMap.count( aAction->m_name ) )
383 {
384 std::pair<int, int> keys = aHotKeyMap.at( aAction->m_name );
385 hotkey = keys.first;
386 alternate = keys.second;
387 }
388
389 aAction->SetHotKey( hotkey, alternate );
390}
~ACTION_MANAGER()
Unregister every registered action.
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 recording actions that have custom UI IDs.
TOOL_MANAGER * m_toolMgr
Tool manager needed to run actions.
void processHotKey(TOOL_ACTION *aAction, const std::map< std::string, int > &aLegacyMap, const std::map< std::string, std::pair< int, int > > &aHotKeyMap)
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 indexing actions by their names.
std::map< int, int > m_hotkeys
Quick action<->hot key lookup.
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 void PromoteUserBoundFrameAction(std::vector< const TOOL_ACTION * > &aGlobalActions, FRAME_T aFrameType, int aMatchedHotKey)
Reorder global actions sharing a hotkey so that one native to the given frame is tried first when the...
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, std::list< TOOL_ACTION * > > m_actionHotKeys
Map for indexing actions by their hotkeys.
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)
static std::string FrameNamespacePrefix(FRAME_T aFrameType)
Return the action-name namespace prefix (e.g.
The base frame for deriving all KiCad main window classes.
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.
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.
const int m_defaultHotKeyAlt
Default hot key alternate.
Base abstract interface for all kinds of tools.
Definition tool_base.h:62
TOOL_ID GetId() const
Return the unique identifier of the tool.
Definition tool_base.h:119
Master controller class:
FRAME_T
The set of EDA_BASE_FRAME derivatives, typically stored in EDA_BASE_FRAME::m_Ident.
Definition frame_type.h:29
@ FRAME_PCB_EDITOR
Definition frame_type.h:38
@ FRAME_SCH_SYMBOL_EDITOR
Definition frame_type.h:31
@ FRAME_CVPCB_DISPLAY
Definition frame_type.h:49
@ FRAME_FOOTPRINT_VIEWER
Definition frame_type.h:41
@ FRAME_SCH_VIEWER
Definition frame_type.h:32
@ FRAME_SCH
Definition frame_type.h:30
@ FRAME_SIMULATOR
Definition frame_type.h:34
@ FRAME_PL_EDITOR
Definition frame_type.h:55
@ FRAME_FOOTPRINT_EDITOR
Definition frame_type.h:39
@ FRAME_GERBER
Definition frame_type.h:53
@ FRAME_CVPCB
Definition frame_type.h:48
@ KICAD_MAIN_FRAME_T
Definition frame_type.h:67
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:45
@ MD_MODIFIER_MASK
Definition tool_event.h:145
@ MD_SHIFT
Definition tool_event.h:139
wxLogTrace helper definitions.